每个程序语言都会有对变量的使用,会对这个变量就行赋值修改等操作。那么问题来了,这些变量都存在哪里,怎样对它进行查找并读取呢?显然,我们必须定义一套规则来操作这些变量简单的进行读取和存储,而这套规则应该就是作用域了。
来看一个简单的赋值语句var a=2;
大多数人会认为这是一个声明变量并赋值而已,事实也确实如此,而要强调的是电脑中的引擎会将它认为是两个声明,第一个var a
看做是在编译器编译时处理,一个是a=2
在引擎运行时处理。通俗的话在概括下就是:为一个变量分配内存,将其命名为a,然后将值2保存进这个变量。当然在编译阶段还有很多任务要做,比如还要查看当前作用域是否声明过此变量等一系列问题。
来看一下如下简单代码:
var a=2;
console.log(a);
引擎是如何处理这段代码的呢?在当前作用域(当前也就是全局作用域)中,引擎会为变量a进行LHS查询和RHS查询。上面的代码var a=2;
会对a进行LHS查询,LHS查询也就是赋值操作的目标是谁。console.log(a)
中会对a进行RHS查询,可以这样理解RHS查询:谁是赋值操作的源头,也就是说这个变量a的源头是谁(值是谁),这里明显就是2了。
为什么要区分LHS和RHS查询呢?因为在变量还为声明的时候,这两种查询的行为不一样。来看如下代码:
function foo(a){
console.log(b); //ReferenceError
}
foo(2);
可以看到在对b进行RHS查询时,变量b并未声明,此时引擎在当前作用域未找到变量b,然后会在外层作用域查找(此时为全局作用域),外层作用域都没有此变量,就会发生常见的ReferenceError。
而在看LHS查询,当LHS查询在此作用域和外层作用域都没有查找到此变量,全局作用域就会自动创建一个此变量,前提是在非严格模式下,如果在严格模式下会报ReferenceError。在ES5中引入的严格模式与正常模式相比在行为上有许多的不同。接下来若查找到此变量,而你要是对此变量做一些不合理的操作就会报TypeError错误。
在来看作用域,看如下代码:
function foo(a){
console.log(a+b);
}
var b=2;
foo(2); //4
可以看到引擎在执行foo函数作用域内的语句console.log(a+b)时b变量并未在此作用域内,而最后还是能输出4,从上面的讲述也已经提到了当引擎未在当前作用域查找到变量就会在它的上层作用域一层层的查找。
而上面我们所手写的作用域叫做词法作用域,引擎在查找变量时会从内部的作用域一层层想外展开来,相应的如果内层的作用域中的变量与外层作用域声明的变量标识符一样的话,内部变量会覆盖外层的,但如果外层作用域是全局作用域可以通过Window.x
来访问,x为相应的变量标识符。
下篇文章会写下eval和with语句对词法作用域的影响和它们对性能是怎么影响的。