JavaScript难点

JavaScript难点_第1张图片
目录

原文链接

1、涉及知识点

JavaScript运行过程、预编译、执行期上下文、作用域、作用域链、立即执行函数、闭包。

2、JavaScript运行过程

JavaScript运行分为3个阶段:

  • 语法分析:JavaScript引擎对你的代码进行分析,检查是否有语法错误
  • 预编译:可以理解为,在内存中开辟一块空间,存放一些变量和函数
  • 解释执行:就是执行代码

3、预编译

了解预编译之前,我们得知道什么是变量提升和函数提升

(1)变量提升和函数提升

  • 所谓提升就是指,变量或者函数在声明之前就使用它

    变量:声明提升
    函数:函数声明整体提升


1

第一个输出:console.log(a) 由于变量是声明提升,所有var a=234; 并不是声明,所以输出undefined

第二个输出:函数声明整体提升,所以有输出。

注意:提升只是预编译过程中的两个现象,要理解预编译,还需要理解一些东西。

(2)全局变量

  • 暗示全局变量

    任何变量,如果该变量未经声明之前就赋值,则此变量为全局对象所有,注意,是任何。

    a=1;
    var b=c=3;
    
  • 一切声明的全局变量,均为window的属性

    
    
JavaScript难点_第2张图片
2
JavaScript难点_第3张图片
3

a是在函数内声明赋值的,因此它的作用域就是函数内部,在外面访问不了,于是输出undefined。

b未经声明就赋值,即为全局对象所有,也就是不需要考虑它是在哪里赋值的。

c本身就是在全局下声明的,自然为全局对象所有。

(3)预编译

  1. 创建AO对象(执行期上下文,先理解成一个对象即可,存放属性和属性值)
  2. 找形参和变量声明,并将其作为AO对象的属性,将属性值设置为undefined
  3. 将实参值和形参相统一
  4. 在函数体里找函数声明(注意是函数声明,区分函数声明和函数表达式的区别),也作为AO对象的属性,属性值即为函数体

预编译发生时间:预编译发生在函数执行前一刻

  • 例子

现在一步步执行上面那个步骤:

//第一步:创建AO对象(执行期上下文,先抽象理解成一个对象即可,
                                存放属性和属性值)
    AO{
                
            }
//第二步:找形参和变量声明,并将其作为AO对象的属性,
        将属性值设置为undefined
     AO{
         a:undefined //形参 ,注意函数内部也有一个 var a 但是注意,
                    因为a 先是形参,一个对象是不能有两个同名的属性的,
                    所以第一步之后AO对象里的 a是找的形参a
         b:undefined //变量声明
     }
//第三步:将形参值与实参相统一
     AO{
         a:1 // 实参是1 ,自此之后不用关注a怎么来的,关注的是 a 值的变化
         b:undefined //变量声明
     }
//第四步:在函数体里找函数声明(注意是函数声明,
区分函数声明和函数表达式的区别),也作为AO对象的属性,属性值即为函数体

上面的函数体中就只有 a 和 d是函数声明, b是函数表达式,不是声明, 
AO对象里已经有属性 a了,因此只需将其属性值改成函数体,
再添加属性 d,将值赋为d的函数体,最终AO对象就是下面的情况
 AO{
     a:function a(){}
     b:undefined 
     d:function d (){}
 }
function  func(a){
      console.log(a); //此时AO里的a为 function a(){},输出
      var a=123;      //将AO里的 a 值改成  123
      console.log(a); //此时 AO里的 a 值为123,所以输出123
      function a(){}; //这句不看,因为在预编译过程已经将它提升了
      console.log(a); //此时AO里的a值还是 123,所以输出123
      var b = function (){}; //将AO里的b值改成function (){} 
      console.log(b); //此时AO里的b值为函数体,因此输出一个函数体
      function d (){}; //这句也不看,因为预编译过程已经提升了
  }
JavaScript难点_第4张图片
4

4、作用域和作用域链

首先在javascript中,我们说一切都是对象,函数也是对象,而且函数是第一类对象,被称作一等公民。对象,有属性,函数也有属性,有些属性是我们可以访问的,有些属性是确实存在但是确不可以被我们拿来访问的,举个例子

function test(){
}

比如属性 test.name test.prototype 分别表示函数名和函数原型,这两个属性是我们可以访问的,test.[[ scope ]] 属性就属于其中一个不可以被访问的属性,scope意为范围,[[ scope ]] 这个属性存放的就是函数的作用域。准确的说存放的是函数的作用域链。

那么什么是作用域,什么是作用域链呢?

作用域:可访问变量,对象,函数的集合

作用域链:[[ scope ]] 中存放着执行期上下文的集合,这个集合呈链式链接,我们把这种链式链接称为作用域链。

但看概念似乎还是有点抽象,首先,从语法上来解读一下作用域链的概念:首先,作用域链由很多执行期上下文组成,这些执行期上下文的又不是散乱的摆放,有一定的位置关系:链式链接。

这里又提到了执行期上下文,也就是上面提到的AO,那么具体什么是执行期上下文呢?

执行期上下文某个函数或全局代码的执行环境,该环境中包含执行代码需要的所有信息。可以简单的认为它就是一个对象

当函数执行时,会创建一个称为 执行期上下文 的对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它创建的执行期上下文会被销毁。

  • 下面通过一个例子来理解作用域,作用域链,执行期上下文的关系。

可以看出,真正的执行期上下文比上面讲到的 AO其实内部存放的东西是更复杂的,上面的AO只是执行期上下文的一种抽象提取。

 

从全局的角度来解读上面的代码:定义了一个函数 a ,声明了并赋值变量glob 。函数a已经定义了,那它就有上面提到的一个函数该有的属性,此时,a 的 [[ scope ]]属性是什么?此时的 a.[[ scope ]] 是一个全局的执行期上下文 GO每次新产生一个执行期上下文,就会放到自己作用域链的顶端,释放的时候也是从作用域链自顶向下释放执行期上下文,即后入先出。

5、立即执行函数

定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作。
只执行一次,执行之后就被释放(销毁)

  • 创建方式
1.(function (){}());W3C建议
2.(function (){})()
  • 执行符号 :()

只有表达式才能被执行符号执行,被执行符号执行的函数会自动忽略函数名
因为执行后被释放了,函数名也访问不了了,所以直接去掉函数名也没什么区别

var test = function (){
    ...
}()
//函数声明:
function test(){
    console.log('a'');
}

//函数表达式举例
var x = function test(){
    console.log('a'');
}

函数声明前加个符号->变成表达式,如+ - ! !! && ||等
但是注意如果是逻辑运算符,操作数数量还得符合该运算符要求,如&& ||

! function test(){
    console.log('a'');
}
function(a,b,d){
    console.log(a+b+c);
}(1,2,3);

系统不会执行,但是也不会报错
系统如何理解:

function(a,b,d){
    console.log(a+b+c);
}

(1,2,3);
//系统将其识别为正常的分开写法

6、闭包

闭包是指可以访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数局部变量,利用闭包可以突破作用域链,将函数内部的变量和方法传递到外部。

  • 闭包的特性:
  • 1、函数内再嵌套函数
  • 2、内部函数可以引用外层的参数和变量
  • 3、参数和变量不会被垃圾回收机制回收
function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        arr[i] = function(){
            document.write(i+  " ");
        }
    }
    return arr;
}
var maArr=test() 
for(var j=0;j<10;j++){
    myArr[j]();
}

执行结果是:10 10 10…10 //总共10个10

分析:

test()的结果是返回一个数组,数组存放的是10个函数体,每个函数的作用是打印一个值,注意只是返回函数体,不是返回函数的执行结果,myArrj;才是执行

test执行结束的判断条件。i==10 终止循环,函数执行完毕,返回10个函数体。

arr[i] = function(){
    document.write(i+  " ");
}

上面的是一条赋值语句
系统在识别的时候只能读到函数的引用,而不管函数体内部的是什么,

arr[i] = function(){
   
}

只有在执行的时候才会去取i值,
10个函数体在外部执行的时候才去访问i,10个函数在执行的时候分别产生一个执行期上下文 iAO, 也就是自己作用域链的顶端,然后访问i值准备将其打印,但是自己的AO里面没有i变量,所以沿着作用域链去找test函数执行时产生的执行期上下文 AO,也就是大家访问的都是同一个执行期上下文,访问的都是同一个i,也就是10

  • 那如何想要在自己想打印的时候打印出0-9呢?(运用立即执行函数)

    function test(){
        var arr=[];
        for(var i=0;i<10;i++){
            (function (j){
                arr[j] = function(){
                    document.write(j+" ")
                }
            }(i));
        }
        return arr;
    }
    var myArr=test() 
    for(var j=0;j<10;j++){
        myArr[j]();
    }
    

    分析:

    返回的arr里面存的是 10个立即执行函数,也就是下面的函数

    (function (j){
        arr[j] = function(){
            document.write(j+" ")
        }
    }(i));
    

    将i作为实参传给形参j ,在i从0-9的循环过程里,依次发生的是

    arr[0] = function(){
        
    }
    arr[1] = function(){
        
    }
    ...
    arr[9] = function(){
        
    }
    

    注意,立即执行,执行的是 function (j){}
    它里面的只是一条赋值语句,对arr数组的每一个元素赋值,也就是一个函数的引用
    这个函数的功能是打印一个值,也就是上面提到的,系统怎么识别语句的问题,此时打印语句没有被执行,当函数在外面执行的时候,就是myArrj时,也就是打印函数的执行,此时它们去访问 j值,于是沿着作用域链去访问 j值,也就是立即执行函数执行时产生的AO,而10个立即执行函数产生的AO是不一样的,里面的j值分别是0-9,所以打印出0-9

  • 闭包的防范

闭包会导致多个执行函数共用一个公有变量,如果不是特别需要,应尽量防止这种情况发生。

你可能感兴趣的:(JavaScript难点)