你想知道关于编译、作用域、闭包吗

226 阅读4分钟

JavaScript笔记01-作用域


  1. 编译原理
  2. 作用域嵌套
  3. 词法作用域
  4. 动态作用域
  5. 闭包
  6. 模块化

一、 编译原理

帮助寻找变量的小助手——作用域

作用域:负责收集和维护由所有生命的标志符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限


  • 分词/词法分析: 由字符组成的字符串分解成(对编程语言来说)有意义的代码块(即词法单元)。

    基本能够知道全部标识符在哪里,以及是如何声明的,从而能够预测在执行过程中如何对他们进行查找。

  • 解析/语法分析:将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树(抽象语法树,AST)

  • 代码生成:将AST转换为可执行代码的过程。

简单说,就是有某种方法将 var = a ;的AST转化为一组机器指令,用来创建一个叫做a的变量存储空间

词法分析和语法分析主要的差异在于词法单元的识别是有状态无状态的方式进行的

另外,RHS和LHS,是指一个赋值操作的右侧和左侧。也就是说,当变量出现在赋值操作的左侧时,进行的是LHS查询;出现在右侧时,进行的是RHS查询。


二、 作用域嵌套

当一个块,或者函数嵌套在另一个块或者函数中时,就发生了作用域的嵌套。

在当前作用域中无法找到某个变量时,引擎就会在外层前天的作用域中继续查找,知道找到该变量,或抵达最外层的作用域(全局)为止。 此时不管有无找到该变量,都会停止查找过程。

三、词法作用域(定义时确定)

就是定义在词法阶段的作用域

词法作用域是由你在写代码时将变量和块作用域写在哪里决定的

因此,当词法分析器处理代码时,会保持作用域不变。(除欺骗词法“with”、“eval”)

  • 性能

eval和with会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。

javascript引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。


动态作用域(运行时确定)

让作用域作为一个在运行时就被动态确定的形式,而不是在写代码时进行静态确定的形式。
动态作用域只关心它们从哪里调用

另外,与动态作用域相关的,就是作用域链。作用域链是基于调用栈,而不是代码中的作用域嵌套。

需要明确的是,事实上JavaScript并不具备动态作用域。它只有词法作用域。但是this机制某种程度上很像动态作用域。

动态作用域和词法作用域的主要区别:

  • 词法作用域是在写代码或者说定义时确定的;动态作用域则是在运行时确定的;

  • 词法作用域关注函数在哪里声明;动态作用域关注函数从哪里调用


闭包

简单来说,就是某变量,在它本身的词法作用域外执行。

只要使用了回调函数都是在使用闭包。

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行;

上面的函数在定义时的词法作用域以外的地方被调用。闭包使函数可以继续访问定义时的词法作用域。


模块化模式

需要具备两个条件:

  1. 要有外部的封闭函数,并且至少被调用一次。(调用一次会创建一个新的模块实例)

  2. 封闭函数返回至少一个内部函数,即本身有多个属性方法,可以外部调用

参考以下现代的模块机制


var myModules = (function Manager(){
    var modules = {};

    function define(name, deps, impl){
        for (let i=0; i<deps.length; i++){
            deps[i] = modules[deps[i]];
        }

        modules[name] = impl.apply( impl, deps);

    }

    function get(name){
        return modules[name];
    }

    return {
        define: define;
        get: get
    };
})();

下面展示如何使用它来定义模块:


myModules.define("bar", [], function(){
    function hello(who){
        return " Let me instroduce: " + who;
    }

    return {
        hello: hello;
    };
} )

myModules.define("foo", ["bar"], function(bar){
    var hungry = "hippo";

    function awesome(){
        console.log( bar.hello( hungry ).toUpperCase() );
    }

    return {
        awesome: awesome;
    };

    var bar = myModules.get("bar");
    var foo = myModules.get("foo");

    console.log(  bar.hello( "hippo" ) 
    ); // instroduce: hippo

    foo.awesome(); // LET ME INTRODUCE: HIPPO
} )