你不知道的JavaScript_作用域_笔记

163 阅读4分钟

作用域

  • 编译

    1. 分词/词法分析
      • 将字符串分解成有意义的代码块,也就是词法单元
      • var a = 2; 分解成: var、a、=、2、;
      • 空格是否会当作词法单元,取决于空格在这门语言中是否有意义
    2. 解析/语法分析
      • 将词法单元流(数组)转换成代表程序语法结构的树(抽象语法树)(AST)
    3. 代码生成
      • 将 AST 转换为可执行代码,与语言、目标平台相关
      • 比如分配内存,存储值到变量等
    • JavaScript 引擎要更复杂
      • JavaScript 的编译过程不是发生在构建之前
      • 大部分情况编译发生在代码执行前几微秒,甚至更短
      • 各种优化,对冗余元素的优化
    • 引擎
      • 负责整个 JavaScript 程序的编译与执行过程
    • 编译器
      • 负责语法分析及代码生成等
    • 作用域
      • 负责收集并维护由所有声明的标识符组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
         var a = 2;
      
      • 编译器首先将这段代码分解成词法单元
      • 之后将词法单元解析成一个数据结构(AST)
      • 首先 var a, 编译器会查看是否已经有一个叫做 a 的变量存在同一个作用域的集合中,如果是,编译器会忽略该声明,继续编译,否则会要求作用域在当前作用域集合中声明一个新变量,命名为 a.
      • 然后编译器为引擎生成运行时所需代码,这些代码开始处理 a = 2 赋值操作, 引擎运行时首先在当前作用域中是否存在一个佳作 a 的变量,如果是,引擎会使用这个变量,如果否,引擎会继续向上一级作用域查找该变量(作用域链)
      • 如果引擎最找找到 a 变量,则赋值为 2,否则引擎抛出一个异常
  • LHS 查询

    • 赋值操作的目标是谁
  • RHS 查询

    • 谁是赋值操作的源头
    • 与查找某个变量的值一样
       function foo(a) {
          var b = a;
          return a + b;
       }
    
       var c = foo(2);
    
       //LHS查询:var c,  a = 2, var b
       //RHS查询: foo(2), = a, return a, b 
    
  • 作用域嵌套(作用域链)

    • 当一个块或函数嵌套在另一个块或函数中
    • 在当前作用域中无法找到某个变量时,引擎会在外层嵌套的作用域中继续查找,知道找到该变量,后代到最外层作用域,也就是全局作用域为止
    • LHS 和 RHS 很重要
      • RHS 在所有嵌套作用域中找不到所需变量时,引擎会抛出 ReferenceError 错误
      • LHS 查询时,如果在顶层(全局作用域)中无法找到目标变量时,会在全局作用域中创建一个该名称的变量,并返回给引擎(非严格模式),严格模式下也会抛出 ReferenceError
      • 如果 RHS 查询到了变量,但尝试对变量的值进行不合理的操作(比如试图对一个非函数类型进行函数调用),引擎会抛出 TypeError
      • ReferenceError 同作用域判别失败相关, TypeError 则代表作用域判别成功,但对结果的操作是非法或不合理的
  • 词法(静态)作用域

    • 闭包的关键
    • 定义在词法阶段的作用域
    • 欺骗词法
      • eval
      • with
      • 影响性能,引擎的优化失效或者不优化
  • 动态作用域

    function foo(){
       console.log(a); //静态作用域: 2, 动态作用域: 3
    }
    
    function bar() {
       var a = 3;
       foo();
    }
    
    var a = 2;
    
    bar();
    
  • 函数作用域

    • 隐藏内部实现
    • 规避冲突
      • 全局命名空间
      • 模块管理
  • 块作用域

    • let
      • 垃圾收集
      • let 循环
    • const
    • with
    • try/catch
      • err 仅在 catch 的块内
  • 提升

    a = 2;
    var a;
    console.log( a );
    
    // 2
    
    console.log( a );
    var a = 2;
    
    // undefined
    
    • 两步
      • 编译阶段: var a;
      • 执行阶段: a = 2;
    • 变量和函数声明从代码中出现的位置被移动到作用域最上面,这就叫做提升
    • 先有声明后有赋值
    • 只有声明会被提升,赋值或其他运行逻辑会留在原地
    • 函数优先
      • 函数首先会被提升,然后才是变量
      foo() //1
      
      var foo;
      
      function foo(){
         console.log(1);
      }
      
      foo = function(){
         console.log(2);
      }
      
  • 闭包

    • 必须要理解作用域
    • 普遍且明显
    • 无处不在
    • 词法(静态)作用域环境中
    • 函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生的闭包
    • 本质是 javascript 中的函数裕兴在它们被定义的额作用域里,而不是他们被执行的作用域里
    • 模块
      • 为创建内部作用域而调用一个包装函数
      • 包装函数必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包
    • 模块机制的核心
      var MyModules = (function Manager(){
         var modules = {};
         
         function define(name, deps, impl){
            for (var 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
      }
      
      ES6 模块