作用域的外表是一对大括号(块作用域)或一个函数(function(){}),其本质是一个按照一组规则对内存中的变量进行查找的工具或者叫工具方法,当程序需要调用内存中某个变量时,就要使用作用域这个工具,从内存中查找这个变量。也有人把它直接理解为一套规则。
编译器通过词法和语法分析,生成代码。编译器在编译的过程中,遇到声明变量的代码时,会调用作用域(工具)的查找方法对待声明的变量进行查找,如果返回是,编译器会忽略该声明,继续进行编译;否则它会调用作用域的声明方法,在当前作用域的集合中声明一个新的变量。
引擎负责编译器和作用域的调度,并执行(这里表意是,把赋值剥离出了执行的范畴)编译好的代码。
更好地认识js语言中的编译
对于var a = 2,事实上编译器会进行如下处理。
1. 遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的
集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作
用域的集合中声明一个新的变量,并命名为a。
2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2 这个赋值
操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a 的
变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量(查看1.3
节)。
let a=1; function foo (){
console.log('a:',a);
let a = 2;
}; foo();//a: undefined
var a=1; function foo (){ console.log('a:',a); var a = 2; }; foo();//ReferenceError: a is not defined
基于编译和执行两个阶段,思考上面两段代码:
有关let有个暂时性死区(temporal dead zone)的概念。 我们都知道(如果不知道就参考《你不知道的JavaScript(上卷)》第七页),var a=2;会在编译阶段先声明a,并初始化为undefined,正因为这个JS存在变量提升问题,所以前一段代码会打印a: undefined,而不是报错。 对于let还有个不会有变量提升的说法,不过这个说法不对。第二段代码,如果完全没有提升,应该打印a:2,之所以报错是因为,let a = 2;编译阶段仍然声明了a变量(因为显然上一级作用域中是有a变量的,并且值为1),只是没有初始化。所以既没有打印a:1,也没有打印a:undefined,而是报错。 这个答案有些地方是自己推测的,分析如有不对,欢迎指正。