全局作用域与遮挡
全局作用域
我们需要明白在作用域链的末尾是整个作用域中很重要的一部分。所有的javascript 运行环境必须隐式的创建一个居于每条作用域链顶部的全局作用域对象(如 浏览器中的window对象,node环境中的global对象):
(global)
↑
someFunc()
/ \
inner() inner2()
↑
foo()
在作用域中,我们需要通过var或let去声明作用域中的变量。如果我们没有用var ,let 或其他方式声明一个对象,那么这个变量被认为是在外层作用域中。
JavaScript的运行环境中通过以下步骤(算法)去分配一个变量。
- 在当前作用域中寻找
- 如果没找到,立即在上一层作用域中寻找
- 如果找到,进行第六步
- 如果没找到,进行第二步操作,直到在全局作用域中找到它。
- 如果在全局变量中没找到,那么在window或global对象中创建它。
- 分配该变量。
通过这种方法,很可能导致声明了一个全局变量(如步骤5)
全局作用域例子
考虑下这段代码:
function someFunc () {
var scopedVar = 1;
function inner () {
foo = 2;
}
}
代码中忘掉了用var 或let 去声明 foo = 2。JavaScript运行环境会遵循上面的算法,首先检查 inner()作用域,然后是someFunc()作用域,再最后是全局作用域。然后第五步就执行了。所以foo就成了一个全局作用域中的变量(window.foo 或 global.foo)。
另一个结果:当我们偶尔忘记使用var时,变量foo就不会被声明在inner()作用域中,那么someFunc函数中就可以访问到它,虽然开发者并不希望它在这里被访问。
谨记:只有内部作用域能够访问外部作用域中的变量。因为someFunc()作用域属于全局作用域内部,所以能够访问全局变量foo。
遮挡
一个变量在上面算法中的第一步中被创建,当使用var或let后,该变量遵循上面的算法进行操作,当执行完毕后,变量则分配在当前作用域中。
我们可以用同一个名称在不同的作用域中有效的声明两个不同的变量:
function someFunc () {
var foo = 1;
}
function anotherFunc () {
var foo = 2;
}
在嵌套作用域中同样有效:
function someFunc () {
var foo = 1;
function inner () {
var foo = 2;
}
}
这被称作遮挡。内部inner()函数中的foo变量遮挡住了someFunc中的foo变量。
遮挡的意思是在inner()作用域中仅仅只能访问它自己的foo变量。没有任何方法去访问声明在someFunc()中的foo变量。
这同样可能成为一个偶然性bug发生的原因,尤其是当有深层嵌套或在长方法中容易出现该bug。