对于变量的作用域(scope),C、Java等语言采取的是“block scope”的方式。与之不同,JavaScript所采取的是“function scope”的方式 — 变量的作用域仅由所处的function决定,与if、for等逻辑块无关。比如,以下这个例子展示了JavaScript中与C、Java等语言不一样的行为:
在C、Java等“block scope”的语言中,if语句、for语句等逻辑块结束后,在这些逻辑块内部定义的变量将会被销毁。JavaScript与之不同,只要一个变量定义在某function内,那么整个function内的所有代码均可访问到该变量,即使这些代码在变量定义之前:
在上述例子中,如果function中a从未被定义,那么console.log(a)将抛出ReferenceError。当function中对a进行定义后,即使这个定义在a变量调用语句之后,对a的调用也属于合法操作(如果对a变量的定义发生在调用语句之后,那么调用语句中a变量的值为undefined)。事实上,在function内用var关键词进行定义的所有变量,其定义操作都会被提至function的开头(赋值操作依然留在var定义的那一行),这在JavaScript中称之为hoisting。比如,上述代码就等价于:
变量的作用域链
联系JavaScript中变量的储存,可以很好的理解JS中的“function scope”与hoisting。由于变量是储存在全局对象或者函数调用对象上的,因此当在function中定义变量时,无论这个变量定义在function的什么地方,这次function调用所使用的函数调用对象中必然会出现一个与此变量同名的属性。如此一来,function中的任何地方都可以访问到该变量。
涉及到函数调用,JavaScript中还有一个更有趣的概念:变量的作用域链 — 由于变量是储存在全局对象或者函数调用对象上的,因此在访问变量时,可以从多个对象上获取值。以下面的代码为例:
在上述代码中2级函数(level-2 function)的内部,当试图访问x变量时,程序可以从3个对象上搜索相应的属性值:调用2级函数所使用的函数调用对象、调用1级函数所使用的函数调用对象、全局对象 — 根据函数定义的嵌套关系,JavaScript将生成一个由全局对象和函数调用对象所组成的对象链。访问变量时,程序将从离访问语句最近的那个对象开始搜索,如果没有搜索到,则在对象链中上一级的对象中继续进行搜索,直至全局对象。
由于这个对象链与变量的作用域有关,因此也叫做“作用域链”。
如果需要临时改变作用域链,将某个对象插入到作用域链的最前端(作为最先访问到的那个函数对象),可以使用with语句:
不过,在JavaScript严格模式下,with语句是被禁用的;即使在非严格模式下,也不推荐使用with语句。