二、JavaScript之作用域
什么是作用域
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
词法作用域
在你不知道的javascript上卷中是这样定义的:
词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。
在JS中词法作用域的规则:点这里
请看下面这段代码:
1 | var value = 1; |
假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
假设JavaScript采用动态作用域,让我们分析下执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。
前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。
再看下面这段代码:
1 | function foo(a) { |
在这个代码示例中有三个固有的嵌套作用域:
- 包围着全局作用域,只有一个标识符: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 | var a = 5; |