上一篇内容讲到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 kk =111之后,但是k却得到了初始值111,打印结果可以确认这一点
  • var ifor (...)的括号内声明,但是在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是一个局部对象的属性了。

参考链接

(未完待续)