【JavaScript】预编译/作用域/闭包的知识点整理

104 阅读5分钟

预编译

函数内部的预编译:函数执行之前要进行的步骤

  • AO activation object 活跃对象,函数上下文

    在全局GO执行的时候,函数执行前一刻,预编译形成的

    函数执行完成后,AO是要销毁的,所以每次执行一次函数都会有一个新的 AO

  • 行程过程/步骤:

    1. 寻找形参和变量声明(变量声明的提升问题)

    2. 实参值赋值给形参

    3. 找函数声明,赋值

    4. 执行函数

    代码中理解:

    function test(a){ // 形参STEP1
      console.log(a); // f a(){} 此时还没赋值a
      var a = 1; 
      // 变量声明STEP1.5
      console.log(a); // 1
      function a(){}  
      // 函数声明STEP3
      console.log(a); //1 此时a已经赋值了
      var b = function(){} 
      // 变量声明STEP1.5
      console.log(b); // f(){}
      function d(){} 
      // 函数声明STEP3
    }
    test(2); // 实参赋值给形参STEP2
    
    /* AO = {
              a : undefined (STEP1) -> 
            2 (STEP2)-> 
            function a(){} (STEP3) ->
            1 (STEP4),
        b : undefined(STEP1) -> function(){} (STEP1.5) -> f(){}(STEP4),
        d : function d(){} (STEP3)
    }*/
    

函数外部的预编译

  • GO global object  全局上下文 GO===window

  • 行程过程/步骤:

    1. 变量声明

    2. 函数声明

    3. 执行

    代码中理解:

    var a = 1;
    function a(){
      console.log(2);
    }
    console.log(a); // 1
    
    //GO = {
    //    a : undefined -> f a(){console.log(2)}
    //				-> 1(执行时赋值)
    //}
    
    console.log(a , b); // f a(){} undefined  !!注意,运行到这一行的时候还没有赋值,不要搞错运行顺序;
    function a (){}
    var b = function(){}
    

GO 和 AO 一起分析:

跟着题目一起分析,巩固知识,再也不怕面试问到啦。
  • 题目一:

    var b = 3;
    console.log(a); // f a(a){console.log(a);... }
    function a(a){
      console.log(a); // f a(){}
      var a = 2;
      console.log(a); // 2
      function a (){}
      var b = 5;
      console.log(b); // 5
    }
    a(1);
    
    // GO = {
    //    b: undefined -> 3,
    //    a: f a(){} -> f a(a){console.log(a);... f a(){...}}
    //}
    // AO = {
    //    a :undefined -> 1 -> f a(){} -> 
    //       2,
    //    b :undefined -> 5,
    //}
    
  • 题目二:

    a = 1;
    function test(){
      console.log(a); // undefined 
      // 注意!:函数体内AO中有 a 的声明,所以不会去外部找 a 所以打印undefined
      a = 2; 
      console.log(a); // 2
      var a = 3;
      console.log(a); // 3
    }
    test();
    var a;
    // GO = {
    //		a: undefined -> 1,
    //    test: f test(){...}
    //}
    // AO = {
    //    a : udnefined -> 2 -> 3
    // 
    //}
    
  • 题目三:

    function test(){
      console.log(b); // undefined
      if (a){
        var b = 2;
      }
      c = 3; 
      console.log(c); // 3
    }
    var a; 
    test(); 
    a = 1; 
    console.log(a); // 1
    // GO = {
    //		a : undefined -> 1,
    //    test : f test(){...},
    //    c : 3,
    //}
    // AO = {
    // 注意:不要管if语句,因为还没有执行,不要管条件是否符合,只要有声明都算
    //		b : undefined
    //}
    
  • 题目四:

    function test(){
      return a; // f a(){}
      //注意: 函数体内有声明 a 所以全局中的a它不会先考虑 return后面的不会执行
      a = 1;
      function a(){}
      var a = 2;
    }
    console.log(test()); // f a(){}
    // GO = {
    //    test : f test(){...},
    //		a : undefined,
    //}
    // AO ={
    //		a : undefined -> f a(){},
    //}
    
  • 题目五:

    function test(){
            a = 1;
      function a(){}
      var a = 2;
      return a; // 2
    }
    cosnole.log(test()); // 2
    // GO = {
    //		test : f test(){...},
    //    a : undefined -> 1,
    //}
    // AO = {
    //		a : undefined -> f a(){} -> 2,
    //}
    
  • 题目六:

    a = 1;
    function test(e){
      function e(){}
      arguments[0] = 2;
      console.log(e); // 2
      if (a){ // 注意!!函数内的 a 为 undefined
        var b = 3;
      }
      var c;
      a = 4; // 赋给函数内的 a 要记得a已经在函数内提前声明了,会先找内部的a
      var a;
      console.log(b); // undefined
      f = 5;
      console.log(c); // undefined
      console.log(a); // 4
    }
    var a;
    test(1);
    console.log(a); // 1 外层的
    console.log(f); // 5 函数内部声明的全局变量
    // GO = {
    //		a : undefined -> 1,
    //		test : f test(){...},
    //		f : undefined -> 5
    //}
    // AO = {
    //		e : undefined -> 1 -> f e(){} -> 2,
    //    b : undefined,
    //		c : undefined,
    //    a : undefined -> 4,
    //}
    

作用域,作用域链

其实函数也是一种对象类型 引用类型 引用值

例如:以下方式可以看出

test.name 函数名,test.length 函数形参个数等属性

在JS中,对象中有一些属性是我们无法访问的,可以理解为JS 引擎内部固有的隐式属性,私有属性,研究意义在于了解其原理。

下面了解一些JS中的隐式属性:

  • [[scope]]

    1. 是函数创建时生成的一个JS内部的隐式属性;

    2. 是函数存储作用域链的容器;

      1. 作用域链Scope Chain:AO,函数的执行期上下文/GO,全局的执行期上下文,【函数执行完成后,AO是要销毁的,所以每次执行一次函数都会有一个新的 AO】
        也就是说,AO 是一个即时的存储容器;它并不是长期存在的,而是根据函数的执行周期而存在的
  • 作用域链

    每个函数在定义的时候,会创建作用域,作用域中存在作用域链,而且会在作用域链里面存储 GO

    • 一开始GO被作用域链的第0位所指,但当函数到被执行的前一刻,预编译生成AO时,此时的GO会被它自身的AO挤下去,并被作用域链的第1位所指,自身的AO才是排在对顶端的位置被第0位所指,(这样也说明了为什么函数最先着自己的东西,而不是外层的东西)
    • 在函数体内,查找变量是到a函数存储的作用域链中,从顶端开始一次向下查找
    • 一个规律:外层执行时,内部被定义

在代码中理解:

```js
function a(){
  function b(){
    function c(){}
    c();
  }
  b();
}
a();
// GO执行:
// 外部开始执行,函数开始被定义:
// a 定义:a.[[scope]] -> 0 :GO
// a 执行:a.[[scope]] -> 0 :a -> AO 
//                       1 :GO
// b 定义:b.[[scope]] -> 0 :a -> AO
//                       1 :GO
// b 执行:b.[[scope]] -> 0 :b -> AO
//                       1 :a -> AO
//                       2 :GO
// c 定义:c.[[scope]] -> 0 :b -> AO
//                       1 :a -> AO
//                       2 :GO
// c 执行:c.[[scope]] -> 0 :c -> AO
//                       1 :b -> AO
//                       2 :a -> AO
//                       3 :GO
// c 结束:c.[[scope]] -> 0 :b -> AO (c -X> AO)
//                       1 :a -> AO
//                       2 :GO
// b 结束:b.[[scope]] -> 0 :a -> AO (b -X> AO)
//                       1 :GO
//        c.[[scope]] X
// a 结束:a.[[scope]] -> 0 :GO (a -X> AO)
//        b.[[scope]] X
// 回到最初a定义的时候的环境
```

```js
function test1(){
  function test2(){
    var b = 2;
    console.log(a);
  }
  var a = 1;
  return test2();
}
var c = 3;
var test3 = test1();
test3();
```

执行上下文、进栈

闭包

相关概念:

  • 当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生原来的作用域链(AO)不释放;【回顾知识点:函数执行后AO会被销毁】

  • 所以,过度地使用闭包可能会导致内存的泄露的这种现象,或者加载过慢;

  • 闭包会产生私有变量,外界无法访问闭包内的变量,只有拽住这个AO的函数才能访问;

    试着理解以下代码:

    function breadMgr(num){
      var breadNum = arguments[0] || 10;
      function supply(){
        breadNum += 10;
        console.log(breadNum);
      }
      function sale(){
        breadNum--;
        console.log(breadNum);
      }
      return [supply,sale];
    }
    var breadMgr = breadMgr(50);
    breadMgr[0](); // 50 + 10 = 60
    breadMgr[1]();//  60 - 1 = 59
    
    //
    function sunSched(){
      var sunSched = '';
    
      var operation = {
        setSched: function(thing){
          sunSched = thing;
        },
        showSched: function(){
          console.log("My schedule on Sunday is " + sunSched);
        }
      }
      return operation;
    }
    var sunSched = sunSched();
    sunSched.setSched('reading');
    sunSched.showSched(); // "My schedule on Sunday is reading"