1

JavaScript 预解析

在了解预解析之前我们先了解下 词法作用域 的概念

词法作用域

1. 什么是作用域

作用域就是指一个变量可以被使用的范围. 即从什么地方开始可以被访问到. 到什么地方结束, 不能在访问到, 这个范围称为变量的作用域.

2. 什么是块级作用域

块级作用域就是使用代码块来限定变量的使用范围.在 js 中( ES5 以前 )没有块级作用域.在 es6 中引入了 let 命令, 来代替 var 声明变量. 而 let 命令声明的变量就具有块级作用域.

所谓的块级作用域就是从变量声明开始, 到变量所在的最近的(最小的)花括号结束为止.

3. js 中的词法作用域

在 js 中 js 的代码需要经过"预解析"( 提前解析 ), 再逐步的解释执行.
所以在 js 中所谓的词法作用域从预解析开始全局起作用, 只有函数可以限制作用域的范围.

代码预解析

1. js 中的声明

声明就是 变量的声明和函数的声明, 其目的是让 js 解释引擎知道有什么东西.
声明时不参与运算的, 是不参与执行的, 是在预解析阶段就完成的.
  • 变量的声明

    // 变量的声明就是 var 变量名.
    var num = 123;
    // 这是一个语法糖,可以理解成
    var num;
    num = 123;
  • 函数的声明

    function 函数名 () { ... }
    //  在一个独立于任何语句( 表达式, if 结构, while 结构 等 )的独立结构中, 或函数中出现的代码, 为函数声明.

2. js 预解析代码如何执行

js 的代码执行要经理两个步骤, 首先是预解析. 预解析会通读所有代码. 如果发现错误则停下, 如果遇到声明则记录.

在声明的时候, 如果是变量名的声明, 解析器内部就会记录下这个变量. 如果使用遍历则检查是否有记录.

在声明的时候, 如果是函数声明, 则 解析器会先记录函数的名字( 相当于变量声明 ), 然后将函数的名字与函数体联系在一起.

在预解析中, 如果出现重复声明, 则第一次声明起作用, 其后所有的同名的声明无效.

// 例如:
  var num = 1;
  var num = 2;
// 等价
  var num = 1;
  num = 2;
声明结束后代码就会再从第一句话开始一句一句的执行.

3. 示例

  • 例子 1

    if ( 'a' in window ) {
      var a = 'hello js';
    }
    console.log( a );

    解析:

    1: 代码需要预解析, 通篇阅读代码, 发现有声明 var a = 'Hello js' 这个语句. 则 js 引擎记录下变量 a
    2: 除此之外没有其他声明预解析结束, 代码等价于
    if ( 'a' in window ) {
      a = 'hello js';
    }
    console.log( a );
    3: 开始执行代码:
    1. 首先判断 'a' in window
    2. 由于预解析时记录了 变量名 a, 表示 a 已经存在. 因此表达式为 true
    3. 进入 if 结构中, 执行赋值语句, a 存储字符串 'Hello js'
    4. 出 if 结构, 打印 a 的值, 结果为 'Hello js'
  • 例子 2

     console.log( func );
     var func = 123;
     function func() {
     console.log( 'Hello js' );
     }

    解析:

    1: 预解析, 发现声明 func 这个变量, js 引擎记录名字 func

    2: 发现声明 function func () ..., js 首先检查名字是否已经记录, 发现已经存在名字 func, 重复声明不再记录名字

    3: 由于声明时函数, 将函数体与已存在的名字 func 相关连, 预解析结束

    4: 代码则等价于:

     console.log( func );
     func = 123;
    5: 执行第一句话, 打印 func, 在 js 引擎中已经将 func 与函数关联, 因此打印函数体
    6: 执行赋值 func 结果为 123

在访问某一个变量的时候, 首先会在当前函数中查找有没有该变量声明,如果有则只在当前函数中访问, 如果没有声明则访问函数外的变量

  • 例子 3

    f2();
    var num = 123;
    function f1 () {
      console.log( num );
    }
    function f2 () {
      var num = 456;
      f1();
    }
    console.log( num );

    解析:

    1: 预解析, 记录下 num, f1, 和 f2, 同时代码 f1 与 f2 与对应的函数体相关联.

    2: 预解析结束, 代码就等价于

    f2();
    num = 123;
    console.log( num );
    3: 开始执行第一句话, 调用 f2. 凡是进入函数体, 又会进行一次预解析

    4: 进入函数 f2, 发现变量 num, 记录下 num. 函数内的预解析结束, 代码变成

    num = 456;
    f1();
    5: 开始执行函数 f2 内的代码, 首先给 num 赋值, num 的取值为 456

    6: 调用 f1(), 进行检查, 当前函数中是否有 f1 的声明? 没有在 f2 中找到 f1 的声明, 因此在外面去找.

    7: 外面有函数的声明, 则调用的是外面的函数, 进入函数 f1, 进行预解析. f1 中没有任何 声明语句则直接开始执行.

    8: 执行代码打印 num. 检查 num 是否在函数内部有声明? 因此到外面去找. 到全局范围去查找, js 引擎中已经记录了名字 num

    9: 由于还没没有执行赋值语句, 打印的结果为 undefined. 函数 f1 执行结束, 回到 f2 中

    10: f2 执行结束, 开始执行 赋值语句 num = 123, 执行完成执行下一句打印, 结果为 123


墨风
76 声望1 粉丝

« 上一篇
常用 CSS 布局