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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。