作用域
章节直通车:
作用域的工作机制分为两种,一种是众所周知的词法作用域,另一个是不太熟悉的动态作用域。下面都具体讲解一下两者的不同之处。
首先具体聊聊词法作用域,这也是大部分语言(包括JavaScript)的一种作用域机制。
词法作用域
词法作用域是在词法分析的时被定义的作用域。在上一篇文章中我们讨论了JavaScript引擎、编译器和作用域之间的关系,知道了词法作用域是一组关于 引擎如何查询变量和他在何处能够找到变量的规则。
词法作用域是在代码被编辑的时候定义的(假定不使用特定代码作弊的前提下,后面章节会涉及到如何作弊)。
比如下面这一段代码:
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar(b * 3);
}
foo( 2 ); // 2 4 12

根据代码和上图所画出的作用域气泡,我们可以很清楚的知道变量分别是属于哪个作用域,这是在代码正在编辑的时候我们就可以明确出来。
上面的函数存在嵌套关系,比如foo函数内又有bar函数,我们可以通过表格更清楚地展示他们之间的所属关系:
气泡id | 所属作用域 | 直属变量 |
---|---|---|
1 | 全局作用域 | foo |
2 | foo的函数作用域 | a, b, bar |
3 | bar的函数作用域 | c |
我们只从bar(b*3)
分析(上面的我们获取到了a为2,b为4),这里首先给c赋值为12,然后需要打印console.log( a, b, c );
。
我们知道bar作用域只有c一个变量,那a和b是怎么查询到的呢?
这就关乎到词法作用域的作用机制,当你在当前作用域找不到需要的变量时,他会向上去查找,直到全局作用域。
比如我们现在要获取a和b的值,当前bar的函数作用域不存在,就会往上找到foo的函数作用域(因为bar被包含在foo的函数作用域内),在foo的作用域同时存在a、b的值,所以就查询到了。
我们将这个函数稍微变一下:
var b = 1;
function foo(a) {
console.log(a);
console.log(b);
console.log(c);
}
foo( 2 );
气泡id | 所属作用域 | 直属变量 |
---|---|---|
1 | 全局作用域 | foo, b |
2 | foo的函数作用域 | a |
函数foo需要分别打印a,b,c三个变量的值,a属于foo函数作用域内的变量可以直接找到,打印a为2;b需要往上找到全局作用域,打印b为1;c往上找,可是全局作用域内不存在c这个变量,所以引擎会抛出错误VM127:3 Uncaught ReferenceError: c is not defined
。
可能有人要问了为什么没有直接给全局变量加入c这个变量,打印为undefined
呢?
这就是上一章的问题了,只有LHS引用才会创建新的变量,这里属于RHS引用,如果查询不到就会抛出异常。
动态作用域
动态作用域和词法作用域的机制完全不同,但是我们仍可以在JavaScript中找到相似的机制,那就是this。
在上一段中我们反复强调了 —— 词法作用域是根据编写时的顺序确定的,而动态作用域是根据调用时的位置所确定的。
我们来分析下面一段代码:
function foo() {
console.log( a ); // 2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
根据词法作用域,这里的a的值获取的是全局作用域中定义的a,值为2;
而在动态作用域中,由于foo是在bar函数中被调用的,这里定义了一个a为3,所以foo中的a指的就是调用点处的a,输出为3。如果调用的地方不存在a的定义,就会到全局作用域中查询 —— 这跟this调用机制简直一模一样!!!
总结
-
作用域分为词法作用域和动态作用域,JavaScript作用域属于词法作用域。
-
词法作用域是根据代码编写时所确认的,动态作用域是根据代码被调用时的调用点所确定的。
-
词法作用域对于变量查询是由内而外的,直到最外层的全局作用域,对于RHS查询,如果查询到最外层全局作用域都没有获取到值,会抛出引用异常错误,对于LHS查询,如果查询到最外层没有获取到,就会在全局作用域中创建当前变量(非严格模式下)。
网友评论