🗒️预处理
两个 JavaScript 语法的全局机制:预处理和指令序言。
Last updated
两个 JavaScript 语法的全局机制:预处理和指令序言。
Last updated
这个机制对于解释一些 JavaScript 的语法现象非常重要。不理解预处理机制,就无法理解 var 等声明类语句的行为。预处理会把作用域提前,有些情况也会赋值。
宏任务中可能执行的代码包括函数、脚本和模块。函数体其实也是一个语句的列表,和脚本和模块比起来,它里面的语句列表中多了 return 语句可以用。函数体、脚本和模块可以使用的语句,如下:
在 JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理,它将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义,但因为一些历史包袱这部分内容非常复杂。
var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值部分,只管在当前作用域声明这个变量。
以上代码声明了一个脚本级别的 a,又声明了 foo 函数体级别的 a,而函数体级别的 var 出现在 console.log 语句之后。但是,预处理过程在执行之前,所以有函数体级的变量 a,就不会去访问外层作用域中的变量 a 了,而函数体级别的变量 a 此时还没有赋值,所以是 undefined。
我们知道 if(false) 中的代码永远不会被执行,但是预处理阶段并不管这个,var 的作用能够穿透一切语句结构,它只认脚本、模块和函数体三种语法结构。所以这里结果跟前一段代码完全一样,我们会得到 undefined。
let 和 const 声明也会被预处理。如果当前作用域内有声明,就无法访问到外部的变量。
function 声明的行为原本跟 var 非常相似,但是在最新的 JavaScript 标准中,对它进行了一定的修改,这让情况变得更加复杂了。
在全局(脚本、模块和函数体),function 声明不但在作用域中加入变量,还会给它赋值。
但 function 声明是在 if 等创建的块级作用域时,预处理只会有变量,但不会提前赋值。
但在 if 创建的块级作用域中,变量是会被提前,且产生赋值效果。
class 声明在全局的行为跟 function 和 var 都不一样。
以上代码执行后,仍然会报错。
如果去掉第四行的 class 声明,则会正常打印出 1,这说明后面出现的 class 声明影响了前面语句的结果。也就是说 class 声明也是会被预处理的,它会在作用域中创建变量,并且要求在定义前访问它时会抛出错误。
class 的声明作用不会穿透 if 等语句结构,所以只有写在全局环境才会有声明作用。class 声明的特征跟 const 和 let 类似,都是作用于块级作用域,预处理阶段则会屏蔽外部变量。
这样的 class 设计比 function 和 var 更符合直觉,而且在遇到一些比较奇怪的用法时,倾向于抛出错误。按照现代语言设计的评价标准,及早抛错是好事,它能够帮助我们尽量在开发阶段就发现代码的可能问题。
此外,class 默认内部的函数定义都是 strict 模式的。