上一篇内容讲到JavaScript的全局对象(Global Object)和全局作用域(Global Scope),这一篇开始讲JavaScript的局部作用域(Local Scope)。
题外话
在【阮一峰的网络日志】看到一篇文章JavaScript 的 this 原理,里面提到了一个问题:
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
为什么上面的代码最后两个foo()
调用输出的结果不一样?JavaScript 的 this 原理一文中给了很详细的解释。这里我套用前一篇文章的观点做一个简单的解释,做一个小小的复习:
- 在JavaScript里面不管是变量还是函数,其实都是某个对象的属性。正因为如此,才可以把一个函数作为值赋给一个变量。
- 上文的
var obj = { foo : function () .... }
创建了一个对象obj
,它具有一个属性foo
,值是一个函数体function () { console.log(this.bar) }
。 var foo = obj.foo
在全局对象创建了一个同样叫做foo
的属性,内容和obj.foo
一致,都是函数体function () { console.log(this.bar) }
- 调用
obj.foo()
的时候,this
指向的是对象obj
,所以输出的是obj.var
的值,也就是1 - 直接调用
foo()
,this
指向的是全局对象,所以输出的bar
值来自于之前的var bar = 2
,也就是2
局部作用域(Local Scope)
什么是局部作用域?先举一个例子:
- 如果直接赋值一个变量,比如
a = 1
,那么这呢个变量就处于全局作用域 - 赋值变量之前用
var
关键字用来声明,比如var b = 1
,那么这个变量就是处于局部作用域。
简单得说,不用var
就是全局作用域,用var
就是局部作用域。显然var
这个关键词像是后来加到语言里面的,之前提到的,JavaScript的设计才花了10天时间的嘛,有疏漏很正常。
像Python那些脚本语言,变量默认是局部的,只有加上
global
关键字才被认为是全局的。
好,那什么是局部作用域?如果全局作用域就是全局对象的属性集合,那局部作用域是不是也是某个对象的属性集合?否则前面说的JavaScript的变量都是某个对象的属性的言论不就不成立了吗?
的确,局部作用域是函数在执行过程中产生的临时对象的属性集合。它并不是Java语言那样,用花括号{}
包围起来的作用域。
看一个例子:
function foo() {
k = 111;
for (var i = 1; i < 3; i++) {
var k;
console.log(k)
k = k + 1
}
console.log(k)
console.log(i)
}
foo() /* 输出
* 111
* 112
* 113
* 3
*/
上面的代码有几点值得注意的地方:
var k
在k =111
之后,但是k
却得到了初始值111,打印结果可以确认这一点var i
在for (...)
的括号内声明,但是在for循环结束之后,console.log(i)
的输出值却是3
如何解释上面的两个行为呢?一步步来:
- 在函数
foo()
运行之前,一个临时的对象被创建起来,这个临时对象的属性集合就是这个函数的局部作用域 - 对于
foo()
里面的每一处用var
声明的变量,在这个临时对象的属性集合里面创建一个相同名字的属性 - 等上述步骤结束之后,开始真正执行
foo()
。所以当执行k = 111
的时候,当前的局部作用域已经有一个名字叫做k
的属性了
再重申下,JavaScript的变量都是某个对象的属性。当foo()函数执行时,有下面这样一个对象链条存在:
全局对象 --> foo()的临时对象
- 用在
foo()
中用var
申明的变量成为foo()的临时对象
的属性 - 而不用
var
申明的变量则是被当作全局对象
的属性 - 查找一个变量的时候先在
foo()的临时对象
的属性集合中找,如果找不到的话再到全局对象
的属性集合中查找
如果混用var
声明变量和非var
申明变量,容易出错,所以JavaScript提供了一种办法帮助你发现这个问题。那就是在脚步的最前面加上一行字符串:use strict
,这样当向非var
申明变量的变量赋值的时候,JavaScript的解释器会报错,用来提醒编写代码的人。
在最顶层使用var
一般来说,在代码的最顶层,也就是全局作用域下使用var
申明一个变量,这个变量会成为全局对象的属性。
在浏览器中运行下面代码:
var a = 1
console.log(a === window.a) // 输出true
在node的命令行输入以下代码:
var a = 1
console.log(a === global.a) // 输出true
但是将上面的代码保存成一个js文件,然后使用``node js文件名来运行这个文件,会发现结果是
false`。原因在About global variables in Node.js里面有说明,Node在执行脚本的时候会在最顶层套上一层函数声明,于是乎实际运行的代码是这样的:
(function (exports, require, module, __filename, __dirname) {
var a = 1
console.log(a === global.a)
});
所以a
是一个局部对象的属性了。
参考链接
- JavaScript Demo: Statement - Var Getting All Variables In Scope
- https://en.wikipedia.org/wiki/ECMAScript#5th_Edition
- Is it possible to access a function local variable from outside of the function?
- Variable Scope (JavaScript)
(未完待续)