最近遇到了一个奇怪的bug,后来发现是作用域的问题,于是想要补下这块的知识,这边文章用来记录,大部分内容来自于你所不知道的JavaScript上卷,加了一点个人的理解,如果有问题麻烦指出来,我会修改的,十分感谢。
第一部分 关于编辑器和引擎和作用域
编译器编译代码
一段代码在正式的被引擎执行之前会被编辑器进行编译,生成引擎能够执行的代码,编译过程大致是两个部分,第一部分是语法分析,第二个部分是代码生成,在语法分析阶段编辑器会将代码分解为一个个词法单元,处理完之后编辑器会将词法单元流转换为树结构(抽象语法树),最后编辑器将抽象语法树转换为可以直接执行的代码。
引擎和作用域
引擎负责全程代码的解析与执行,作用域则用于维护所有变量的查询并确定不同代码的访问的权限
引擎、作用域、编辑器三者间的交互
var a = 2
console.log(a)
编译过程中的变量
对于这一段代码在编辑器在编译完成之后,引擎在执行的时候找a这个变量的方式分为两种,在声明变量 var a = 2的时候,引擎会执行两个操作,首先声明这个变量,其次将2赋值给这个变量,声明这个操作是在编译期进行,在编译的时候编译器会将变量声明放入对应的作用域中。
执行过程中的变量
在赋值的时候引擎会根据作用域对变量进行查询,在这里分为两种查询RHS,LHS。第一种查询RHS是对变量的值进行查询,LHS则是在赋值的时候查询变量容器的位置,在非严格模式中LHS会沿着作用域链一直往上寻找变量位置,到了最外层之后没有找到这个变量,那么编译器就会创建一个全局变量来存放对应的值。RHS则会沿着作用域链条一直向上寻找变量,如果到了最外层任然没有发现变量,引擎则会抛出一个ReferenceError的错误。
变量提升
var a = 10;
function test(){
console.log(a)
a = 100;
console.log(a);
var a;
console.log(a);
}
test();
上面的代码中的输出为 undefined 100 100 在编译的过程中一共声明了两个a变量,第一个a变量是在全局变量中声明了,第二个变量声明在了test函数中,在编译之后的代码相当于:
var a = 10;
function test(){
var a;
console.log(a)
a = 100;
console.log(a);
console.log(a);
}
test();
在这个过程中仿佛声明的语句被提升到了作用域的顶部,所以叫做作用域提升,函数声明也是存在变量提升的:
console.log(a)
console.log(b)
var b = function b(){
console.log(2)
}
function a(){
console.log(1)
}
输出结果 fa() ; undefined, 这里可以看出只有函数声明存在函数变量提升而函数表达式与变量提升是相同的,而当一个函数的声明的函数声明与变量同名的时候函数表达式优先:
a()
var a = function b(){
console.log(2)
}
function a(){
console.log(1)
}
这里的输出结果是1
RHS导致的变量泄露
上面介绍了当编辑器在程序运行的时候会沿着作用域链条寻找变量,在非严格模式下如果知道最外层任然没有找到对应的 变量那么引擎会在最外层的作用域中重新声明一个变量来接收这个数值:
b()
function b(){
a = 5
console.log(a)
}
console.log(a)
在这段代码中在程序运行时引擎根据作用域查找b中的变量a,因为a并未显示的声明所以引擎会根据作用域层层的寻找这个数值,一直到最外层没有找到a,最后在全局声明了一个变量a,这就导致了本来只能在函数b的作用域内部显示的变量a能够在全局之中被访问到。