JS面试大全

目录

目录

js介绍

js书写

前端页面的组成

耦合

let/ var / const 声明变量

var和let区别

六种基本数据类型

基本类型里的null和undefined的区别

三种引用数据类型/复杂数据类型

四种判断数据类型和缺点

循环

1. while循环

 2.do while循环

3.for循环

跳转语句

1.终止循环

2.跳出循环

常见判断语句

1.条件判断语句

2.嵌套条件判断语句

3.三目运算

4.复合条件表达式 

常见的比较运算符

模板字符串

字符串常用方法

对象常用方法

数组常用方法

函数

函数定义

函数的特点 

函数的参数

函数定义方式

声明 / 表达式 / 自调用 / 回调 

递归过程解析

无限级递归目录树 

构造函数原理解析

构造函数使用优化

不一样的闭包理解

vue中其实大量运用了闭包,比如我们常见的data:

JS内存泄露

内存生命周期

两种垃圾回收算法

引用技术垃圾回收机制

标记清除法

JS内存泄漏的场景

箭头函数使用的注意事项

预解析

作用域

全局作用域

局部作用域

块级作用域

为什么需要块级作用域

 全局变量和局部变量同名的坑

事件委托

事件冒泡和捕获/阻止冒泡

为什么要阻止冒泡

三种本地存储的方式 

三种存储方式区别

this指向的问题

改变this指向的方法

call,apply,bind

1、相同点

2、不同点

深浅拷贝

明白堆和栈的区别

传值和传址

防抖与节流

原型

了解原型前我们先认识js的两种对象 

普通对象object和函数对象function

 原型的写法

 为什么使用function中的prototype? 为什么要继承

 理解构造函数和原型对象的区别

 理解原型链

常见的几种继承方式

1.原型继承

2.构造继承

3.组合继承

解构赋值

1.数组的解构

1.嵌套解构

2.剩余运算符

3.默认值

2.对象的解构赋值 

3.字符串解构

执行上下文和轮询机制

执行上下文


js介绍

    全称javascript
    在客户端运行的脚本语言
    书写规范由 ECMA 制定

        1)ECMAscript      ----- javascript
                                      前者是后者的语言标准,规定了JS编程语法和基础核心知识
         2)DOM                ----- 页面文档对象模型  
                                    提供了JS很多操作页面元素的属性和方法
                                             
          3)BOM             ----- 浏览器对象模型
                  提供了很多操作浏览器的属性和方法,这些方法都存放在window浏览器对象上
                  DOM和BOM 合起来就是Web API,提供一套操作浏览器功能和页面元素的API.

                  BOM指浏览器对象模型将浏览器的各个组成部分封装成对象.    
                  包含:
                    window对象;
                    Navigator对象;
                    Screen对象;
                    History对象;
                    Location对象;
                    存储对象;

js书写

    1)内嵌式              书写在script标签内部
    2)行内书写           比如在标签内部οnclick=""
    3)外链/外部引入      通过script标签来引入,

前端页面的组成

    html    结构层   页面的布局,结构
    css     表现层   控制样式
    js      行为层   页面的功能

耦合

耦合(可以称为关联性)
1、耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。
2、在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应使类和构件之间的耦合最小。
3、分类:有软硬件之间的耦合,还有软件各模块之间的耦合。耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接

let/ var / const 声明变量

1.var 声明

   var 声明是全局作用域或函数作用域 。可以变     量提升,值可以被改变

2.let 声明

   块级作用域 ,值可以被改变但是没有变量提升,存在暂时性死区,必须先声明后使用。

3.const 声明

    1).const 声明常量,声明后不可修改
    2).声明常量必须要立即初始化。    
    3).与let一样,只有块级作用域生效,不存在变量提升,存在暂时性死区。
    4).不可以重复声明
    5).const实际上保证的不是变量的值不能改动,而是变量指向的那个内存地址所保存的数据不得改动。
     对于基本数据类型,因为值保存在变量指向的那个内存地址。所以得出,const对基本数据类型指向的是:指向一个地址
     对于复合数据类型,保存的也是一个实际数据的指针,但是内部数据的结构不能控制。

var和let区别

//let
function  aFun1(){
    // i 对于for循环外的范围是不可见的(i is not defined)
    for(let i = 1; i<5; i++){
        //  i只有在这里是可见的
    }
    // i 对于for循环外的范围是不可见的(i is not defined)
}


//var
function aFun2(){
    // i 对于for循环外的范围是可见的
    for(var i = 1;i<5; i++){
        // i 在for 在整个函数体内都是可见的
    }
    // i 对于for循环外的范围是可见的

六种基本数据类型

            // js当中有6中基本数据类型
			
            1.字符串数据类型
			// string类型  字符串数据类型   由'' 或者""  包裹着,那么就是字符串类型
				var userName = '李昌';
				// var userName2 = 李昌; 

            2.数字类型
			// number类型 数字类型   只要是纯数字,没有其他符号干扰,那么就是数字类型
				var num1 = 123;
				console.log('num1的值为:',num1);
		
            3.布尔值 	
			// Boolean类型 布尔值 只有两个值,  要么true,要么false,基本上只会出现在判断上面,
			// true     成立 / 是
			// false    不成立/ 否
				var c = 9<10; 
				console.log('c的值为:',c);

            4.未定义	
			// undefined  未定义 未赋值, 没有赋值的变量,或者传参的函数.
				var d;
				console.log(d);
				
            5.空类型
			// null 空类型   多用于清空变量,或者对象
			 	var f = 1;
				console.log('f的值为:',f);
				 f = null;
				 console.log(f);

            6.symbol   
           //symbol  独一无二的值

				Symbol:独一无二的值
					let a= Symbol();
					let b= Symbol();
					//console.log(a == b)
					//false

				Symbol使用1:当做属性名使用,前提是需要赋值给变量 
					let name = Symbol();
					let age  = Symbol();
					let obj = {
						[Symbol]:'亮',
						[Symbol]:25
					}
					//console.log(obj)
					//{Symbol(): 亮, Symbol(): 25}
					
					//conslole.log(obj[name])
					// 郑明亮
				Symbol使用2:消除魔术字符串
					//魔术字符串:在代码中多次出现,与代码形成强耦合的某一个具体的字符串或数值
					//风格良好的代码应尽量下消除魔术字符串,用含义清晰的变量代替
					let a = {
					  blue: Symbol("Foo"),
					  yellow: Symbol("Foo")
					}
					
					let m = "China"
					//if判断
					if (m.length > 0) {
					  m = a.blue
					} else {
					  m = a.yellow
					}
					//switch判断
					switch (m) {
					  case a.blue:
						console.log("hello")
						break
					  case a.yellow:
						console.log("world")
					}



	 
			// 基本数据类型 都存储在栈内存当中

基本类型里的null和undefined的区别

  • undefined 表示一个变量自然的丶最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态

  • 在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显示的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可,释放后不占用内存

三种引用数据类型/复杂数据类型

// Array 数组  用中括号[]包裹,那么就是数组,每一项之间用逗号隔开
			var arr = ['周雪婷',121,true,null,undefined];
			console.log(arr);
		
		
		// function 函数 
			
			function 自定义的名字(){
				
			}
		
		// object 对象, 简写obj , 只要你发现,用大括号包裹着的数据,那么就是对象,万物皆对象
			var obj = {
				userName:'姚明',
				userAge:20,
				userSex:'女',
				userHobby:'男'
			}
			
			
			
			/**
			 *  var obj ={
				 * 
				 * 	键名/属性名: 值,
				 *  键名/属性名: 值,
				 * 
				 * }
			 }
			 * **/
 
			
			// 引用数据类型, 存储在堆内存当中

四种判断数据类型和缺点

            1.typeof
            //判断数据类型    typeof
            //缺点:typeof 可以准确地判断出基本类型,但是对于引用类型除了function之外返回的都是                         
            //object,不能进一步判断它们的类型

                var age = 18;
				console.log(typeof age);   //number


            2.instanceof
			//检测数据类型	   instanceof    
            //返回的是一个布尔值,只能判断引用数据类型
            //缺点:instanceof 不能区别 undefined 和 null而且对于基本类型如果不是用new 声明的                     
            //则也测试不出来,对于是使用 new 声明的类型,它还可以检测出多层继承关系
            var  arr = [1,23,4,5];
			var obj = {
				name:'姚明',
				age:18
			}
			console.log(arr instanceof String);  //false
			console.log(obj instanceof Object);  //true
			console.log(arr instanceof Object);  //true
											     //
			var num = 1;                         //
			console.log(num instanceof Number);  //false

    
            3.constructor指向对象的构造函数
            constructor 属性返回创建该对象的函数,也就是构造函数。

            "zs".constructor                 // 返回函数 String()  { [native code] }
            (5.2).constructor                 // 返回函数 Number()  { [native code] }
            false.constructor                  // 返回函数 Boolean() { [native code] }
            [1,2,1].constructor              // 返回函数 Array()   { [native code] }
            {name:'zs', age:16}.constructor  // 返回函数 Object()  { [native code] }
            new Date().constructor             // 返回函数 Date()    { [native code] }
            function () {}.constructor         // 返回函数 Function(){ [native code] }



            4.万能的Object.prototype.toString.call()

            toString()是Object原型对象上的方法,返回的是代表该对象的字符串。
            调用Object.prototype.toString方法返回的格式是[object Xxxx],其中xx就是对象类型。
            而对于其他对象(Array、String、Number、Boolean、RegExp、Date等),则需要通过call、     
            apply来调用才能返回正确的类型信息。

循环

1. while循环

            1.while 循环
                 while 包含一个循环条件和一段代码块,只要条件为真,就能不断执行
                
                 结构
                 while(条件){
                    条件为真执行代码;
                 }
                 
                 
                 死循环  会一直执行大括号里面的内容,就叫做死循环
                 
                 while(true){
                     为真就会执行大括号里面的内容.
                 }

 2.do while循环

    do while 循环
                do..while循环 和while 几乎一样,平时可以相互替代使用,但是dowhile 有一个特点,                    就是无论 成立与否,都会执行一次
              
                结构:
                 do {
                     //循环体
                 }while(条件)

3.for循环

    for循环
                结构:
                for(初始表达式;条件表达式;递增表达式){
                    循环体,循环执行的操作
                }
                初始表达式:确定循环变量的初始值,只在循环开始执行一次
                条件表达式:每轮循环开始时,都要执行这个条件表达式,只有表达式结果为真,也就是成立,才可以执行
                递增表达式:每一轮循环结束后,都要执行的一个操作,可以是累积的和,也可以是减
                
                
                for循环嵌套for循环
                    外面的for循环,循环一次,里面的for循环要全部执行一次.

跳转语句

1.终止循环

     终止循环  break
                在循环体当中出现,就会立即终止循环,并且跳出循环体,代表循环结束
                不仅可以在条件判断中使用(switch),还可以在循环中使用
                break会改变预定义的循环次数.
                跳出循环
        

2.跳出循环

     continue    
            立即执行下一次循环,在循环体当中,可以使用continue,如果使用,那么会跳出本次循环
            立即执行下一次

常见判断语句

1.条件判断语句

    .s在判断一个表达式的布尔值,这个表达式总有成立或者不成立,根据条件成立或者不成立执行不同的语句:
                布尔值
                    true    条件为真/条件成立
                    false   条件为假/条件不成立
            
                代码结构:
                    if(布尔值){
                        条件成立时执行语句
                    }
                
                简便写法
                    if(布尔值)
                        条件成立时执行语句
                    
                双分支(无论条件成立或者不成立,都有执行的语句)           
                    if(布尔值){
                        条件成立时执行
                    }else{
                        条件不成立时执行
                    }
                
                多个条件判断
                    else if(另一个条件判断){
                        条件成立执行语句
                    }

2.嵌套条件判断语句

    代码结构:
                    if(条件1){
                        满足条件1执行;
                        
                        if(条件2){
                            既满足条件1也满足条件2执行;
                        }else{
                            满足条件1,但是不满足条件2执行;
                        }
                        
                    }else{
                        不满足条件1执行;
                    }

3.三目运算

          条件?条件成立时执行语句:条件不成立时执行;
                ? 前面就是条件表达式 后面 条件成立时执行
                : 后面是条件不成立时执行;

4.复合条件表达式 

                ||     表示或  前后两个条件满足1个.就会执行
                &&  表示且  前后两个条件必须全都成立才可以执行
                
                
                if(条件1 || 条件2){
                    
                }
                
                if(条件1 && 条件2){
                    
                }

常见的比较运算符


                运算符就是咱们数学中学习的各种计算符号
                javascript常用的运算符和操作符总结:


                类别                       操作符
                算数运算符            +、-、*、/、%(求余数,取模)
                字符串操作符          +字符串拼接
                逻辑操作符            !(非)、&&(与/且)、||(或)


                一元操作符             ++ 、 -- (自增自减运算符)

                ++在前先赋值后运算,++在后先运算后赋值


                比较操作符            < > >= <= != == ===  
                赋值操作符            =   复合赋值(+= -=)

模板字符串

字符串增强

  • 可换行

  • 可使用插值表达式添加变量,变量也可以替换成可执行的 JS 语句

   let str = `<div>生成一个随机数:${ Math.random() }</div>`;

字符串常用方法


    1.concat();           链接两个,或者多个字符串,返回新的字符串,如果合并多个字符串,用逗号隔开
    2.includes();         用于查找字符串中是否包含指定子字符串,如果包含,返回true,否则.false
    3.split();            把字符串分割成字符串数组
    4.charAt();           返回指定位置的字符,从0开始,下标
    5.charCodeAt();        返回指定位置字符的Unicode编码
    6.formCharCode();   把Unicode编码返回为咱们需要的字符串
    7.indexOf();         返回指定字符串值在字符串中首次出现的位置,如果没有,返回-1
    8.lastIndexOf();     返回字符串值在字符串中最后一次出现下标,如果没有,返回-1;
    9.slice();            提取字符串片段,并在新的字符串中返回被提取的部分,原来的字符串没有变化
    10.startsWith();     查找字符串是否以指定字符串片段开头,返回布尔值
    11.substr();         从起始索引提取字符串中指定长度的字符,返回新的字符串
    12.substring();        提取字符串片段
    13.toLowerCase();      转化为小写
    14.toUpperCase();   转化为大写
    15.trim();             去除首尾的空白格;
    16.search();         用于检索字符串中指定的子字符串,如果存在,返回下标,不存在,返回-1
    17.replace();       替换字符串中的内容,默认只替换第一次出现的参数,而且默认区分大小写.
    
 

对象常用方法

   1.in                               key in object 自由和继承属性都返回true
    2.hasOwnProperty             key in object 继承属性判定为false,自有属性判定true
    3.for in                      对象遍历
    4.object.keys()              将对象的key生成数组
    5.object.values()            将对象的value生成数组
    6.object.entries()            将对象key和value生成二维数组
    7.object.assign(a,b)           合并对象
    8.object.defineProperty()    直接再对象上定义新的属性或修改现有属性,并返回该对象
    9.object.defineProperties()  直接再对象上定义多个新的属性或修改现有属性并返回该对象
    10.object.is()                判断两个值是否相等,返回布尔值

数组常用方法

    join()          数组的所有元素由指定的字符分割,然后合并成字符串

    flat(Infinity)   将多维数组处理成一维数组

    array.of()         生成数组
    concat()        合并多个数组
    indexof()          搜索某个元素 在数组中第一次出现的位置(下标) 如果没有返回-1
    lastIndexOf()   返回指定元素在数组中最后一次出现的位置/下标,如果没有返回-1
    reverse()          颠倒/反转排列数组,会改变原来的数组,返回的也是新数组
    unshift()         数组头部添加元素,返回新数组的长度,原数组会发生变化
    shift()         头部删除,返回删除元素,改变了原来的数组
    push()          尾部添加,会改变原数组
    pop()             删除数组最后一个元素,会改变原来的数组,返回删除的元素
    splice()           替换,能添加,能删除
    sort()            排序,默认升序,也就是正序
    forEach()         循环遍历数组
    slice()          截取选取数组中的一部分,返回新的数组,原数组并不会改变
    includes();       判断一个数组是否包含一个指定的值,返回的是一个布尔值
    toString();         转换为字符串
    every();          检测数组中的元素是否符合条件,返回的是布尔值,每一项都要去判断是否符                             合条件,只要有一项不满足,返回就是false
    some();             检测数组中的元素是否满足条件,只要有一项满足,那么返回true
    filter();            创建一个新的数组,然后把符合条件的元素,放到这个数组里面,形成一个新的                          数组,原数组没有改变
    isArray();         判断是否是一个数组,返回的是布尔值
    map();             返回一个新的数组,数组中的元素为原始数组元素调用函数处理后的值;
    find();             返回符合条件的第一个元素,如果有了,则返回,没有呢,返回undefined
    findIndex();     返回第一个符合条件的元素下标,原数组没有影响,如果没有,则返回-1;

函数

函数定义

把一段相对独立的具有特定功能的代码块封装起来,形成一个独立的实体,就是函数
                起一个名字(函数名),在后续开发中可以反复调用.
                为什么要使用函数
                    1)避免代码一打开就直接执行,根据需要执行代码
                    2)封装一段代码,根据需求重复利用.

函数的特点 

                1)函数声明的时候,函数体并没有直接执行,只有当函数被调用的时候才会执行.
                2)函数一般都是用来完成某一件事情,或者某一个操作来使用的

函数的参数

           1.函数的参数
                在使用函数的时候,我们可以传递一些值,这些值就是参数,
                在函数中,如果要传递多个参数,每个参数之间用逗号隔开,
                而且,参数是有顺序的,
            2.形参
                在声明一个函数的时候,为了函数灵活使用,有一些值是没法确定,模糊的,
                可以给函数声明一个参数,或者多个参数,这些参数是没有具体的值的,仅仅占位作用,
                函数声明中的参数就是形参.
            3.实参
                函数声明时,如果设置形参,在函数调用时传入的参数,我们叫做实际参数,也就是实参
                函数调用时的参数就是实参.
            
            4.函数传参
                一般情况下,声明函数中如果有参数,那么在函数调用时也会传入参数,
                参数是不会限制个数的,同时也不会限制参数的类性,而且,每个参数之间用逗号隔开,
                function 函数名(参数名,参数名,参数名....){
                    
                }
                通过length,可以知道有几个参数
            
            5.return 返回值;    
                    return 后面可以跟变量名,表达式,数值,字符串,
                    return后面是什么东西,就返回什么,相当于指定返回.
                    通常情况下,return下一行的代码不再执行.
                    
                    
                    
            6.arguments对象   针对函数参数的一个操作,通过[]来获取具体的某一个参数.

函数定义方式

声明 / 表达式 / 自调用 / 回调 

//js中每个函数都有一个Arguments对象实例arguments,

//它引用函数实参,可以用数组下标的方式引用 

1.函数声明

        function hello(){}

2.函数表达式 

         var hello =function(){}

3.自调用函数

        (function(){})()

4.回调函数

        把函数当参数传递,叫做回调函数

递归过程解析

        函数自己调用自己,称为递归函数,但是递归函数一定要有结束条件否则会一直循环下去

        如:function getSum(n){

                if(n >=1) {

                        return n +getSum(n-1)

                    }

                   return 0

                }

                getSum(5)

         //递归函数执行过程

         //getSum(5)-> 传入5 ->if成立->返回 5+个体Sum(5-1)->等待的过程

         //getSum(4)-> 传入4 ->if成立->返回 4+个体Sum(4-1)->等待的过程

         //getSum(3)-> 传入3 ->if成立->返回 3+个体Sum(3-1)->等待的过程

         //getSum(2)-> 传入2 ->if成立->返回 2+个体Sum(2-1)->等待的过程

         //getSum(1)-> 传入1->if成立->返回 1+个体Sum(1-1)->等待的过程

         //getSum(0)-> 传入0->if不成立->返回0;结束整个过程

         //过程结束前的一次执行结果为5+4+3+2+1 = 15,最后一次等于0,递归结束。

             c(9)
			 
            //递归九九乘法表

			 function c(num){
				 if(num ==1){
					 console.log('1X1=1')
				 }else{
					 c(num-1)
					 for(var i=0,str='';i<num;i++){
						 str+=`${num}x${i}=${num*i}  `
					 }
					 console.log(str)
				 }
			 }

            //console
	      1X1=1
	      2x0=0  2x1=2  
		  3x0=0  3x1=3  3x2=6  
		  4x0=0  4x1=4  4x2=8  4x3=12  
		  5x0=0  5x1=5  5x2=10  5x3=15  5x4=20  
		  6x0=0  6x1=6  6x2=12  6x3=18  6x4=24  6x5=30  
		  7x0=0  7x1=7  7x2=14  7x3=21  7x4=28  7x5=35  7x6=42  
		  8x0=0  8x1=8  8x2=16  8x3=24  8x4=32  8x5=40  8x6=48  8x7=56  
		  9x0=0  9x1=9  9x2=18  9x3=27  9x4=36  9x5=45  9x6=54  9x7=63  9x8=72  

无限级递归目录树 

	<div class='ids'>1</div>
		<script type="text/javascript">
			var data = [{
					name: 'IT互联网',
					child: [{
						name: '编辑语言',
						child: [{
								name: 'java'
							}, {
								name: 'c#/.net'
							}, {
								name: 'python'
							},
							{
								name: '前端开发',
								child: [{
									name: 'jq'
								}, {
									name: 'vue'
								}, {
									name: 'react'
								}]
							}
						]
					}]
				},
				{
					name: 'IT互联网',
					child: [{
						name: '编辑语言',
						child: [{
								name: 'java'
							}, {
								name: 'c#/.net'
							}, {
								name: 'python'
							},
							{
								name: '前端开发',
								child: [{
									name: 'jq'
								}, {
									name: 'vue'
								}, {
									name: 'react'
								}]
							}
						]
					}]
				}
			]


			function createTree(arr) {
				var str = `<ul>`
				arr.map(item => {
					str += `<li>${item.name}`
					if (item.child) {
						str += createTree(item.child)
					}
					str += `</li>`
				})
				return str += `</ul>`
			}
			document.querySelector('.ids').innerHTML = createTree(data)

构造函数原理解析

       在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般           大写 

        构造内部原理三部曲:

        1.在函数体内最前面隐式的加上this={}

        2.执行this.xxx=xxx

        3.隐式的返回this

        

        function Person(name, gender, hobby) {

           //隐式 this={}

           //执行中
          this.name = name;
          this.gender = gender;
          this.hobby = hobby;
          this.age = 6;

            //return this 隐式
        }

        var p1 = new Person('zs', '男', 'basketball'); // 创建一个新的内存 #f1

        var p2 = new Person('ls', '女', 'dancing');  // 创建一个新的内存 #f2
        var p3 = new Person('ww', '女', 'singing');  // 创建一个新的内存 #f3


        构造函数会有以下几个执行过程

         1.当以 new 关键字调用时,会创建一个新的内存空间

         2.函数体内部的 this 指向该内存

         3.执行函数体内的代码,给实例添加属性

         4.由于函数体内部的this指向新创建的内存空间,默认返回 this ,就相当于默认返回了该内存空间,也就是上图中的 #f1。此时,#f1的内存空间被变量p1所接受。也就是说 p1 这个变量,保存的内存地址就是 #f1,同时被标记为 Person 的实例。

          以上就是构造函数的整个执行过程

        

构造函数执行过程的最后一步是默认返回 this 。言外之意,构造函数的返回值还有其它情况。下面我们就来聊聊关于构造函数返回值的问题。

(1) 没有手动添加返回值,默认返回 this

function Person() {
 this.name = 'laowang';
}

var p1 = new Person();

当用 new 关键字调用时,产生一个新的内存空间 1,并标记为 Person 的实例然后函数体内部的 this 指向该内存空间 1;执行函数体内部的代码;由于函数体内部的this 指向该内存空间1,而该内存空间又被变量 p1所接收,所以 p1 中就会有一个 name 属性,属性值为 ‘laowang’
 

p1: {
 name: 'laowang'
}

(2) 手动添加一个基本数据类型的返回值,最终还是返回 this

function Person() {
 this.size= 12;
 return 999;
}

var p1 = new Person();
console.log(p2.size);   // 12
console.log(Person());   // 999
p2: {
 size: 12
}

(3) 手动添加一个复杂数据类型(对象)的返回值,最终返回该对象

function Person() {
 this.size= 66;
 return [1, 2, 3];
}

var p1 = new Person();
console.log(p1.size);  // undefined
console.log(p1.length);  // 3
console.log(p1[0]);      // 1

 (4) 如果指定了返回对象,那么,this对象可能被丢失。

function Person(name){
  this.name = name;
  this.say = function(){
    return "I am " + this.name;
  }
  var that = {};
  that.name = "It is that!";
  return that;
}

var person1 = new Person('nicole');
person1.name; // "It is that!"

new是生成新的内存空间,并且有值返回,而不new的话,this会指向window,不会默认返回,除非有return(显性返回)

构造函数使用优化

为了防止因为忘记使用new关键字而调用构造函数,可以加一些判断条件强行调用new关键字,代码如下:

function Person(name){
  if (!(this instanceof Person)) {
    return new Person(name);
  }
  this.name = name;
  this.say = function(){
    return "I am " + this.name;
  }
}

var person1 = Person('nicole');
console.log(person1.say()); // I am nicole
var person2 = new Person('lisa');
console.log(person2.say()); // I am lisa

不一样的闭包理解

闭包就是能够读取其他函数内部变量的函数。
由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在 调用后被自动清除。
闭包的作用就是:局部变量无法共享和长久的保存,而全局变量可能造成变量污染,当我们希望有一种机制既可以长久的保存变量又不会造成全局污染,但是由于变量被引用着所以不会被回收,这是优点也是缺点,不必要的闭包只会徒增内存消耗
            function f1() {
                var a = 10;
                return function() {
                    a++;
                    console.log(a);
                };
            };
            var f = f1();   //f =11
            f();                 //f =12
            f();                 //f =13

 下面的闭包结果很多小伙伴有点蒙,为什么是这个情况呢?为什么a明明在函数作用域内,为什么a的值不会被重置?

	function f1() {
				var a = 10;
				return function() {
					a++;
					console.log(a);
				};
			};
			var f = f1();
			f(); // 11
			f(); // 12
			var x = f1()
			x() //11
			x() //12
			x() //13

实际上,当f()在不断调用时,a的状态会被保存,不会在f1()执行完后释放,F实际就是f1内函数的执行结果,并且这个函数对局部变量存在引用,形成了闭包的包含关系且内存无法释放。

vue中其实大量运用了闭包,比如我们常见的data

为什么要把data定义为函数而不是一个对象?

本来它应该是一个对象,那我直接的写一个对象不行吗,我为什么要以这样一种方式写呢?

如果不在组件里面 这样写,我们都写一个data对象的话,那么相当于它就是一个全局的,你如果在你当前的组件里面定义了key=1,我在我的组件里面也定义了key的值,那么你会发现你的这个值会被我定义的给覆盖掉,但是我们要是借助闭包,每次定义的赋值的对象都会被独立维护,闭包之间的变量不会互相影响,这样就可以放心的对对象内的属性进行定义和维护

	       
            new Vue({
				el:'',
				data:{
					key:1
				}
			})
			
			
			data(){
				return {
					key:2
				}
			}

JS内存泄露

无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃

内存生命周期

一般按照顺序分为三个周期:

        1.分配期:分配所需要的内存,在前端javascript中,内存是自动分配的

        2.使用期:使用分配到到得内存(读写),在js中读写一个变量或者对象的属性值

        3.释放期:不需要时将该内存释放和归还,js会自动释放内存,(除闭包和部分bug                         外),内存泄漏也是出现在这个时期,内存没有被释放导致的

两种垃圾回收算法

引用技术垃圾回收机制

对象 = null 这个对象就被回收了

标记清除法

 在变量进入执行环境时,标记为“进入环境”,当变量离开执行环境时。标记为“离开环境”,被标记“进入环境”的变量不会被回收,被标记“离开环境”的变量则被回收。

环境可以理解为我们的作用域。但是全局作用的变量在页面关闭时才销毁

JS内存泄漏的场景

1、被遗忘的计时器,写在函数内部,闭包内的
2、意外的全局变量{目前eslint会提示语法错误}
3、被遗忘的事件监听器,使用监听器后要移除监听器
4、被遗忘的ES6 set成员
5、被遗忘的订阅发布事件监听器
6、被遗忘的订阅发布事件监听器
7、被遗忘的闭包

箭头函数使用的注意事项

箭头函数和普通函数的区别

  • 语法更加简洁,清晰

  • 箭头函数不会创建自己的 this

  • 箭头函数继承而来的 this 指向永远不变

  • call / apply / bind 无法改变箭头函数中 this 的指向

  • 箭头函数不能作为构造函数使用

  • 箭头函数没有自己的 arguments

  • 箭头函数没有自己的原型

  • 箭头函数不能用作 Generator 函数,不能使用 yeild 关键字

预解析

个人理解预解析的意义:如果是一个军队,每个人都有自己的职位。那么打仗前,指挥官需要进行点名确定每个人是否在位。这样打起仗来才可以保证每个岗位都有人正常运转,如果通信兵生病了,而指挥官不知道,那么打仗的时候就会无人通信,影响到整个战役。

预解析分为:变量预解析、函数预解析

(1) 变量提升 所有的变量声明提升到当前作用域最前面 不提升赋值操作
(2) 函数提升 把所有的函数声明提升到当前作用域的最前面 不调用函数

      函数提升的优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被同名变量赋           值后覆盖

预解析:

    //console.log('a1==',a)
    // a == undefined;


    var a =10
    //console.log('a2==',a)
    // a2 == 10


    (1).提取全局变量及全局函数
    (2).赋予给了window对象
    (3).先声明 var a
    (4).因为a没有值,所以赋予undefined
    (5).在使用过程中,a = 10,等号赋予了值

作用域

作用域分为全局,局部,和函数作用域:
它是指对某一变量和方法具有访问权限的代码空间 , JS , 作用域是在函数中维护的。表示变量或函数起作用的区域 , 指代了它们在什么样的上下文中执行 , 亦即上下文执行环境。
var 声明的是全局, 但是如果在函数内声明 var 这个 var 只能在函数作用域
内使用。超出这个范围无效
let 局部作用域
经典面试题
  for ( var i = 0 ; i < 5 ; i ++ ){
     setTimeout ( function (
     console . log ( i )
 ), 1000 )
}
这个循环里, i 的输出值为多少?
答案 : i = 5 ,输出了 五个 5
解答:首先,延时事件属于异步函数,会放到事件队列内执行, for 循环属于同
步事件,在执行栈内进行优先操作,当 i = 4 的时候, i < 5 , 但是由于 i ++ 的存在,
i 变成了 5 ,因为 var i = 5 , var 是全局变量,而在一秒之后,延迟事件,因为
每次循环就执行一次延迟事件对吧? 所以这里一共有五个延迟事件, 01234
所以会输出五个延迟事件对吧? 延迟事件的 i 是全局变量,所以输出 5 5
那么如何将延迟时间打印的值变为 01234
第一,用 let let 会生成局部作用域,互不干涉
第二,闭包,用闭包将延迟事件包裹,形成函数作用域,函数作用域是私有变
量,即输出 01234

全局作用域

全局作用域:变量在函数外定义为全局变量,全局变量有局部作用域:网页中的所有脚本和函数均可使用

  • 最外层函数和在最外层函数定义的变量拥有全局作用域

  • 所有未定义直接赋值的变量自动声明为拥有全局作用域

  • 所有 window 对象的属性都拥有全局作用域

局部作用域

 局部作用域:变量在函数内声明为局部作用域

块级作用域

块级作用域

  • 块级作用域的函数在预编译阶段将函数声明提升到全局作用域,并且会在全局函数声明一个变量,值为undefined,同时也会被提升到对应的块级作用域顶层

  • 块级作用域函数只有定义声明函数的那行代码执行过后,才会被映射到全局作用域

  • 块级作用域函数只有执行函数声明语句的时候,才会重写对应的全局作用域上的同名变量

为什么需要块级作用域

为什么需要块级作用域?

  • 变量提升导致内层变量可能覆盖外层变量

  • 用来计数的循环变量泄漏为全局变量

 全局变量和局部变量同名的坑

 在全局变量和局部变量不同名时,其作用域是整个程序。
 在全局变量和局部变量同名时,全局变量的作用域不包含同名局部变量的作用域。

事件委托

  即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的是事件冒泡。

  比如一个宿舍的同学同时快递到了,一种方法就是他们一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一 一分发给每个宿舍同学;

  在这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
 



		<h1 class="show"></h1>
		
		<ul class="ul1">
			<li>去淄博</li>
			<li>吃烧烤</li>
			<li>喝啤酒</li>
			<li>八大局</li>
			<li>吃水果</li>
			<p>拔牙</p>
		</ul>
		
		
		<script>
			var show = document.querySelector('.show');
			var ul1 = document.querySelector('.ul1');
			// var lis = document.querySelectorAll('ul li');
			// 原始写法
				// for(var i =0;i<lis.length;i++){
				// 	// 取出来每一个li标签,然后给li标签添加点击事件
				// 	lis[i].onclick = function(){
				// 		show.innerHTML = this.innerHTML;
				// 	}
				// }
			// 事件委托 事件代理  给父元素绑定事件, 代理子元素的事件  通过e.target 事件源,找到具体发生事件的元素
		
			ul1.onclick = function(e){
				var e = e || window.event;
				// e.target   事件源 具体发生事件的元素本身
				console.log(e.target);
				show.innerHTML = e.target.innerHTML;
			}
		
		
		
		</script>

事件冒泡和捕获/阻止冒泡

    当事件发生后,这个事件就要开始传播。为什么要传播呢?因为事件源本身并没有处理事件的能力。例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身不能处理这个事件,事件必须从这个按钮传播出去,从而到达能够处理这个事件的代码中(例如我们给按钮的onclick属性赋一个函数的名字,就是让这个函数去处理该按钮的click事件)。

    当事件在传播过程中,找到了一个能够处理它的函数,这时候我们就说这个函数捕捉到了这个事件。

    为了更好地理解冒泡的概念,我建议你现在想象一下你的面前放着一杯水,但这杯水和我们平时看到的有点点不同,它分为数层,每一层又分成一或多个区域,最顶层是我们熟悉的窗口对象(即window对象),下一层分为好几个区域(document对象、history对象等等),而document对象的下一层又分为多个子对象。这些对象的层次关系构成了DOM中的对象树。

    冒泡这个过程很有意思。其实就像是一杯水,但是这杯水是分层次的,最底下是当前触发事件的对象。然后越往上范围越大,最顶层肯定是window,倒数第二层是document。气泡在上浮过程中会判断当前所到达的层有没有绑定事件处理方法。有话就执行相应的处理。没有的话就继续起泡。直到到达最顶层的window窗口层。我们可以在任何一层做相应的处理以阻止事件继续起泡。
 

为什么要阻止冒泡

一个例子,

  盒子one中有一个盒子two,盒子two中有一个button上面绑着事件a,

  而这个相同事件恰巧在盒子one和two中也有,

  当button事件被触发时,one和two中的事件也会被触发,

  但是我们只希望触发所点击的事件,而不是都触发

  所以我们需要进行阻止。

   阻止的三种方法:

      第一种:event.stopPropagation();
      第二种:return false;
      第三种:event.preventDefault();

三种本地存储的方式 

    1.cookie就是用来存储数据信息的
                必须设置过期时间,必须使用国际时间或者格林尼治时间
                存储大小只有5kb左右

    2.localStorage 比cookie更加受开发者喜欢
                存储在本地浏览器中,存储数据以键值存在,可以存储大量数据
                永久存储,不手动删除,将会永远存在,所以安全性要相对低一些;

    3. sessionStorage 和 localStorage 存储用法一模一样,
            但是,存储大约5mb左右,只兼容ie8以上的浏览器
            sessionStorage  属于临时存储,浏览器一旦关闭,自动清空

三种存储方式区别

            1.cookie 存储在用户本地终端的数据
                有时候也有cookies,用的最多的就是在用户登录,
                存储账号密码
            2.Html5提供了新特性里,包含两种本地存储,也就是localStorage以及sessionStorage,
            localStorage,sessionStorage 以及cookie


            共同点:
                都是存储在浏览器当中,同源


            区别:
                cookie始终在同源http请求中携带,即使不使用,也会携带浏览器与服务器之间传递,
                而且有路径这个概念,存储数据还小,而且必须设置时间
                localStorage 永久存储,存储的数据大一些,约为5mb,
                sessionStorage 临时存储,页面关闭即清除存储.
                

this指向的问题

  • 函数预编译过程 this 是指向 window

  • 全局作用域 this 指向 window

  • call/apply 可以改变函数运行时 this 指向

  • obj.func(); func里面的 this 指向 obj

  • 谁调用当前事件,this就指向谁

  • 箭头函数里面的 this 和内部 arguments,由定义时外围最接近一层的非箭头函数的 arguments 和 this决定其值

  • 箭头函数不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数替代(...x)

  •       // rest 参数 :接受所有实参 组成数组 
          function fn(...y){
          //console.log( "接受所有实参 ,组成伪数组",arguments ); 
                console.log(y); 
              //[2,3,4,5,6,7,8]
              //[2,3,4]
            }
            fn(2,3,4,5,6,7,8)
            fn(2,3,4)

改变this指向的方法

call,apply,bind

1、相同点

  1. 三个都是用于改变this指向;
  2. 接收的第一个参数都是this要指向的对象;
  3. 都可以利用后续参数传参。  

2、不同点

  1. call和bind传参相同,多个参数依次传入的;
  2. apply只有两个参数,第二个参数为数组;
  3. call和apply都是对函数进行直接调用,而bind方法不会立即调用函数,而是返回一个修改this后的函数,如 bind(obj,'1')()

深浅拷贝

明白堆和栈的区别

其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。 堆和栈都是内存中划分出来用来存储的区域。

栈(stack)为自动分配的内存空间,它由系统自动释放;

堆(heap)则是动态分配的内存,大小不定也不会自动释放

传值和传址

栈: 按值存储的; 基本数据类型;

堆: 按地址存储; 复杂类型,

指针:地址,指向堆内存数据本身一个地址;

引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配

在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中

所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。

但是引用类型的赋值是传址例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响

浅拷贝,拷贝的只是栈内存里面的一个地址,若其中一个值变化,另一个值也会同步变化
深拷贝,将数据进行递归遍历,每次遍历都判断是否为基本类型,若不是,就一直循环,至到变成基本类型为止
深拷贝,就是在堆内存里重新开辟一个独立的空间,将拷贝的对象的所有属性拿到新开辟的控件内,互为独立不干涉。
最简单的深拷贝方法 JSON.parse(JSON.strigify()),但是不能将函数深拷贝,只用于对象或者数组
function  cloneDeep ( obj ){
 	//判断obj是否是对象,或者为空直接返出
     if(typeof obj !== 'object' || obj == null){
         return obj
     }
    	
     //初始化值
    var a = Array.isArray(obj)?[]:{};
     
    for(let key in obj){
        //判断obj的这个key如果是对象,那么递归
        if(obj[key] && typeof obj[key]=='object'){
            a[key]=fn(obj[key])
        }else{
        //如果不是,那么直接用 变量 a 收集
            a[key]=obj[key]
        }
    }
     //将a返出
     return a
 }

防抖与节流

防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行第一次/最后一次,如果在 n 秒内又触发了事件,则会重新计算
函数执行时间。
防抖函数分为非立即执行版和立即执行版。

		//非立即版
function debounce2(func, wait, num) {
    let timeout;
    return function () {
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(() => {
            //func.apply(this)// ****无效****
            //func.apply(this, arguments);
            func.call(this, ...arguments);
       }, wait);
   }
}
//立即
function debounce(func,wait) {
    let timeout;
    return function () {
        if (timeout) clearTimeout(timeout);
        let callNow = !timeout;
        timeout = setTimeout(() => {
            timeout = null;
       }, wait)
        if (callNow) func.apply(this, arguments)
   }
}
			 
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。
​​​​​​​
//时间戳版
function throttle2(func, wait) {
    let previous = 0;
    return function() {
    let now = Date.now();
    if (now - previous > wait) {
        func.apply(this);
        previous = now;
    }
   }
}
//定时器版
function throttle(func, wait) {
    let timeout;
    return function() {
    if (!timeout) {
    timeout = setTimeout(() => {
    timeout = null;
        func.apply(this, arguments)
    }, wait)
    }
   }
}

原型

了解原型前我们先认识js的两种对象 

普通对象object和函数对象function

prototype和__proto__概念明确:
        通过new Function创建的对象都是函数对象,
        其他都是普通对象(包括构造函数)(通常通过Object创建)可以通过typeof来判断
       1. prototype是函数才有的属性:
        每一个函数对象都有一个 prototype 属性,但是普通对象是没有的;
        prototype下面又有个 constructor,指向这个函数。 
        2.__proto__是每个对象都有的属性:
        每个对象都有一个名为__proto__的内部属性,指向它所对应的构造函数的原型对象,            原型链基于__proto__;
        function f1(){};
        typeof f1 //"function
        //构造函数是 构造函数是生成对象的模板,一个构造函数可以生成多个对象,每个对象             都有相同的结构。 构造函数自身的属性和方法无法被共享
        var o1 = new f1(); //函数实例
        typeof o1 //"object"
        var o2 = {};
        typeof o2 //"object"

 原型的写法

            //定义原型对象
            function Cat(name, age, hobby) {
				this.name = name,
				this.age = age,
				this.hobby = hobby
				this.eat = function() {
					return '鱼'
				}
			}
            //原型对象中添加方法和属性
			Cat.prototype.sleep = function() {
				return '8h'
			}
            Cat.prototype.sex= 'boy'

			var f1 = new Cat('花花', 1, '睡觉')
			 
			f1.sleep()
			console.log(f1)

 

 为什么使用function中的prototype? 为什么要继承

因为工厂模式下,我们需要生成多个相同功能的实例

比如这种情况,我们用构造的方法非常合适,不仅方便,且每个生成的实例都与原型有关联,不过类似eat这种方法,每次返回的值都是固定的,非常浪费性能,可以采用sleep的方式,在原型对象中添加eat方法,这样实例如果有需要可以自行调用,而不是直接在实例化的时候就拥有

            //定义原型对象
            function Cat(name, age, hobby) {
				this.name = name,
				this.age = age,
				this.hobby = hobby
				this.eat = function() {
					return '鱼'
				}
			}
            //原型对象中添加方法和属性
			Cat.prototype.sleep = function() {
				return '8h'
			}
            Cat.prototype.sex= 'boy'

			var f1 = new Cat('花花', 1, '睡觉')
			var f2 = new Cat('豆豆', 2, '打豆豆')
			var f3 = new Cat('狗蛋', 3, '吃饭')
			var f4 = new Cat('二黑', 4, '摆烂')
            ....+999

            
 

 理解构造函数和原型对象的区别

在上面的例子中,Cat是我们的构造函数,Cat是一个生成对象的模板,每次new的时候,会固定返回模板内定义的结构。

而Cat.prototype,他是Cat这个函数的  “原型,对象”

为什么要分开写,首先每个函数都有自己的prototype,这个prototype上面可以挂载需求的方法和属性定义。但是同时,它也是一个 ‘拥有对象属性’的 原型

原型对象内可以以对象的形式挂载需要定义的方法和属性

 理解原型链

  特性:如果自己有就用自己的,没有就一级一级向上查找

			//首先定义一个构造函数 F1
			function F1() {
				this.name1 = 'f1'
			};
			
			//F1的原型上 定义name = 'object'
			F1.prototype.name = 'object';

			//接下来定义 构造函数F2和F3
			function F2() {
				this.name2 = 'f2'
			};

			function F3() {
				this.name3 = 'f3'
			};
			
			//F2的 原型指向了 F1
			//就理解成F2对象 通过改变this指向,现在可以使用F1内定义的方法和属性了,他不想努力了
			//F3就更爽了,它的原型指向F2,F2呢刚认识大哥F1,现在F3有了F2,F1的方法和属性
			//简单来说 就是前人栽树,后人乘凉了
			F2.prototype = new F1(); //F2的原型是F1
			F3.prototype = new F2(); //F3的原型是F2
			
			//接下来将F3实例化 赋值到F上
			var F= new F3(); //实例化的处理
			F.name1; //打印的到f1
			//因为F是构造函数生成的对象模板,所以他没有prototype,
            //只有__proto__,通过它来找到原型(寻亲)
			//此处为修改F1原型name的值
			//而这个一直通过__proto__寻找直到找到name的过程
			//我们称之为原型链
			F.__proto__.__proto__.__proto__.name = '666';

常见的几种继承方式

1.原型继承

通过prototype的方式,将构造函数原型对象改变到指定原型,继承此指定原型的方法和属性。

优点:同一个原型对象

缺点:不能修改原型对象,变化后会影响所有实例 

function Animal(){
 this.type = "动物"
};
function Cat(name,color){
 this.name = name;
 this.color = color;
};
Cat.prototype = new Animal();
var c1 = new Cat('x','白色');
var c2 = new Cat('t','花色');
c1.type  //动物

2.构造继承

在构造器内,通过改变this指向获得另一个构造器的成员

优点:不存在修改原型对象影响所有实例,各自拥有独立属性
缺点:无法继承父类原型上的属性与方法
通过 apply/call 只能拷贝成员,原型对象不会拷贝
function Animal(){  
   this.type = "动物"
};
function Cat(name,color){       
   Animal.apply(this);   //将Animal对象的成员放到Cat对象上
   this.name = name;
   this.color = color;
};
var cat1 = new Cat("大明","黄色"); 
var cat2 = new Cat("小明","白色");
cat1.type = '我是黄猫';
cat2.__proto__.type = '我是动物';
console.log(cat1.type); //'我是黄猫' cat1被修改
console.log(cat2.type); //"动物"

3.组合继承

通过构造函数实现了属性的继承,再通过原型链实现了原型方法和属性的继承

将借用构造和继承原型合二为一,既保证原型上定义的方法实现复用,又保证每个实例有自己属性

function Animal() {
	this.type = '动物'
};

Animal.prototype.eat = function() {
	console.log('吃')
};

function Cat(name, color) {
	this.name = name;
	this.color = color;
	Animal.call(this);
};
//直接把cat的原型对象指向new Animal
Cat.prototype = new Animal();

var cat1 = new Cat("大明", "黄色");
var cat2 = new Cat("小明", "白色");

cat1.type = '我是黄猫'; //修改当前构造器中的属性

cat2.__proto__.type = '我是动物'; //修改了原型对象的值,但并不影响cat1,cat2的值

console.log(cat1.type); //'我是黄猫'    
//此处是打印了构造函数内的type,并非原型原型对象的值变化,并不影响构造函数的值

console.log(cat2.type); //'动物' //因为cat2的构造器内 首先有自己的type,所以先打印自己的
//此处是打印了构造函数内的type,并非原型原型对象的值变化,并不影响构造函数的值

console.log(cat2.__proto__.type); //'我是动物 //当你打印原型时,值就是刚才的 我是动物

cat1.eat(); //此外还可以调用原型对象中eat()方法

解构赋值

1.数组的解构

数组的解构赋值 ,解构不成功显undefined(不完全解构)

1.嵌套解构


    //左右两边的格式要相同
    let[a,[[b],c]]=[1,[[2],3]]
    // a=1,b=2,c=3
    
    let[,,c]=[1,2,3]
    //console.log('c:',c)
    // c == 3

2.剩余运算符


    let [a,...b]=[1,2,3,4,5]
    //console.log('a:',a)
    // a  ==  1
 
    //console.log('b:',b) 

    // b  ==2,3,4,5

3.默认值

 
     解构赋值允许指定默认值
     // 默认值   ===> 在没有赋值的情况下才是用默认值
     //如果有了新的值之后,新的值会覆盖默认值。
     
     // let [a, b = 10] = [1];
     //  a=1,b=10
     
     // let [a, b = true] = [1, 2];
     //  a=1,b=2
     
     let [a = b, b = 1] = [];
     console.log('a:', a, "b:", b);  
     //报错,因为b没有值。 

2.对象的解构赋值 

无次序,对象取值,变量必须与属性同名。

1.// 对象的解构赋值
    let { 
        age: age222,
        name
    } = {
        name2: "吴克勤",
        age: 18
    }
    //console.log("name222:", name);
    // 此时name =undefined
    
    // 当解构赋值右边name和左边name 名称相同时,打印
    let { 
        age: age222,
        name
    } = {
        name: "吴克勤",
        age: 18
    }
    //console.log("name222:", name);
    //name222 == 吴克勤
    //说明,打印的数据 'name' 是 '属性值'
    //即,打印了对象的-----属性值
    //属性名 和 值 是对应打印的
    
2.// 如果解构失败,变量的值等于undefined。
    let {
            foo
    } = {
        bar: "abc"
    }
    //  console.log("foo:", foo);  // undefined
    
3.如果变量名与属性名不一致,必须规范格式,如:

   let obj = {
        foo: 'aaa',
        baz: 'bbb'
    }


    let { foo: baz } = obj
    //一般来说,解构赋值变量名与值默认相等,这样取解构值时最方便
    // console.log('baz:', baz)
    // baz 值为 aaa

    // 因为 foo同名解构到的值是 ‘aaa',此时aaa赋值给了baz,baz就是aaa

    // 如果 obj.foo = undefined

    //   let obj = {    foo: undefined ,   baz: 'bbb'   }
    //   let { foo: baz=1 } = obj

    // 此时 打印baz  =1
 

   // 如果 obj.foo = undefined

    //   let obj = {    foo: undefined ,   baz: 'bbb'   }
    //   let { foo: baz  } = obj

    // 此时 打印baz  = undefined

   

4.默认值
    let { a = 3 } = {};
    //console.log('a:', a);
    // a= 3
    
    let { a =3 } =  { a:  5 }
    //console.log('a:', a);
    // a= 5  a默认值等于3,解构对象后重新赋值,变为5
    
    let { a:s } = { a:5 }
    //console.log('s:', s);
    // s=5
    
    //正确格式
    //console.log('a:', s);
    // s =5
    
5.引用地址在堆内存中,他们地址是不同的,因为开辟了新的空间
  // console.log([] === [])
  //  fasle

6.代码块之间解构会报错
  let x;
     //报错
    {x}={x:1}
        //正确
    ({x}={x:1})

3.字符串解构


    因为字符串中有length属性,因此类似于数,所以可以对这个属性进行解构     赋值
    let [a, b, c, d, e] = "wukeqin";
    //console.log("a:", a, "b:", b, "c:", c);
    //  a= w,b=u,c=k

执行上下文和轮询机制

执行上下文

什么是执行上下文:就是当前js代码执行和解析时存在的环境,只要有js代 码,必然运行在 执行上下文中

拓展:

宏任务:setTimeOutsetIntervalsetImmediate(异步)

微任务:promise.then,process.nextTickObject.observe、MutationObserver

  • **注意:Promise是同步任务

    • 图例+文字解释
  • ​ 

     

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值