二、JavaScript之作用域

什么是作用域

作用域是指程序源代码中定义变量的区域。

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

词法作用域

在你不知道的javascript上卷中是这样定义的:

词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。

在JS中词法作用域的规则:点这里

请看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
var value = 1;

function foo() {
console.log(value);
}

function bar() {
var value = 2;
foo();
}

bar(); // ???

假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

假设JavaScript采用动态作用域,让我们分析下执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。
再看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
function foo(a) {

var b = a * 2;

function bar(c) {
console.log( a, b, c );
}

bar(b * 3);
}

foo( 2 ); // 2 4 12

在这个代码示例中有三个固有的嵌套作用域:

  • 包围着全局作用域,只有一个标识符:foo。
  • 包围着作用域 foo,它含有三个标识符:a,bar 和 b。
  • 包围着作用域 bar,它里面只包含一个标识符:c。

当我们执行console.log(…)语句的时候,开始查找三个被引用的变量 a,b 和 c。它首先从最内部的作用域气泡开始,也就是 bar(…) 函数的作用域。在这里它找不到 a,所以它向上走一层,到外面下一个最近的作用域气泡,foo(…) 的作用域。它在这里找到了 a,于是它就使用这个 a。同样的事情也发生在 b 身上。但是对于 c,它在 bar(..) 内部就找到了。

如果在 foo(..) 内部定义一个变量 c,console.log(…) 语句也仍然会找到并使用 bar(…) 中的那一个,而不会使用 foo(…) 中的那一个。

一旦找到第一个匹配,作用域查询就停止了。相同的标识符名称可以在嵌套作用域的多个层中被指定,这称为“遮蔽(shadowing)”(内部的标识符“遮蔽”了外部的标识符)。无论如何遮蔽,作用域查询总是从当前被执行的最内侧的作用域开始,向外/向上不断查找,直到第一个匹配才停止。

注意:全局变量也自动地是全局对象(在浏览器中是 window,等等)的属性,所以不直接通过全局变量的词法名称,而通过将它作为全局对象的一个属性引用来间接地引用,是可能的。

1
2
3
4
5
6
var a = 5;
function foo(a) {
console.log(a);
console.log(window.a)
}
foo(2) // 2,5

参考文献

https://github.com/mqyqingfeng/Blog/issues/3