Javascript知识梳理 - 预编译、作用域/作用域链/闭包

259 阅读4分钟

预编译

知Javascript预编译(分为全局预编译和函数预编译),彻底理解作用域、作用域链和闭包。

JS脚本全局执行逻辑:

  1. 通篇检查语法错误
  2. 全局预编译
  3. 全局代码执行

函数执行逻辑:

  1. 函数预编译
  2. 函数代码执行

NOTE:全局预编译在全局执行的前一刻开始,函数预编译在函数执行的前一刻开始

1、变量声明、函数声明提升

变量申明提升、函数声明提升是在预编译过程中;先变量声明提升(变量赋值是在相应代码行执行时完成),再函数申明提升(函数整体提升)

2、全局预编译

全局上下文(Global Object:全局对象)即window,在全局预编译过程中产生,在全局执行中动态补充完整,全局执行完毕即销毁。

全局上下文动态生成步骤:

  • 1、变量申明
  • 2、函数声明
  • 3、全局代码执行
console.log(test)
var test = 1;
function test() {}
test = 2;

<!--
全局上下文(将GO当作对象看待)中的test属性的值在预编译、全局执行的过程中如下变化:
GO = {
    test: undefined -> 
          function test(){} ->
          1 ->
          2
}
-->

3、函数预编译

函数执行期上下文(Activation Object:活跃对象)在函数预编译中产生,函数执行过程中动态补充完整, 函数执行完毕后销毁。

函数执行期上下文动态生成步骤:

  • 1、形参和变量声明
  • 2、实参赋值形参
  • 3、函数申明
  • 4、函数代码执行
function test(a) {
    var a = 1;
    function a() {}
    a = 2
}
test(3);
<!--
 AO = {
    a : undefined -> 
        3 ->
        function a(){} ->
        1 ->
        2
 }
-->

4、作用域、作用域链、闭包

4.1 作用域、作用域链

所谓的GO、AO即是全局作用域、函数作用域。在JS中,函数是特殊的对象,也有属性,如:name、length、prototype等。对象有些属性只有JS引擎能够调用,称为隐式属性。函数定义时(函数申明在预编译过程中定义,函数表达式在执行时定义)生成一个隐式属性[[scope]],该隐式属性保存的就是作用域链,作用域链可理解为是一个引用了相应的函数作用域和全局作用域的栈,内层作用域存在于栈顶,最外层作用域(GO)存在于栈底。

  • 1、函数定义时的作用域链引用了所有父级作用域
  • 2、函数预编译时产生的执行期上下文压入栈顶
  • 3、函数执行完毕后,自身的执行期上下文从栈中弹出,若该执行期上下文没有其他引用,则被销毁回收。
var a = 'window';
function test1(){
   var b = 'test1';
   console.log(a);
   function test2(){
      var c = 'test2';
      console.log(b);
   }
   return test2;
}
var test3 = test1()
test3();

案例执行具体流程:

  1. 全局预编译:GO, test1.[[scope] -> GO
  2. 全局执行:
  3. 函数test1预编译:test1.[[scope] -> AO(test1)/GO, test2.[[scope] -> AO(test1)/GO
  4. 函数test1执行完毕并返回test2:test1.[[scope]] -> GO, test2.[[scope] -> AO(test1)/GO
  5. 函数test3,即test2预编译: test1.[[scope]] -> GO, test2.[[scope] -> AO(test2)/AO(test1)/GO
  6. test3执行完毕:test1.[[scope]] -> GO, test2.[[scope] -> AO(test1)/GO ; “AO(test2)销毁”
  7. 全局执行完毕:AO(test1)销毁, GO销毁。

变量查找的过程就是沿着作用域链由栈顶到栈底在每个作用域中查找的过程,所以内层函数能够访问外层函数中的变量,而外层函数却访问不到内层函数中的变量。

4.2 闭包

定义(来自MDN):函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

从闭包的定义来看,闭包是一种正常现象:只要定义了函数,就产生了闭包,就使得函数可以访问外部作用域。这让人有似曾相识的感觉,这不就是函数作用域链做的事嘛。个人理解:闭包是概念(是函数的重要特性),实质是作用域链(作用域链的生成只与函数定义时的位置有关)。

5. 总结

Javascript预编译涉及到作用域、作用域链、闭包等重要知识。函数和闭包相辅相成,理解了闭包这一特性,才能正确地使用函数。此外,在非严格模式下,普通函数单独执行的情况时,this的指向都是window。