JavaScript 的作用域是词法作用域(Lexical Scope) ,函数的作用域在定义时就已经确定,而不是在调用时。
来看一个例子:
scss
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar(); // 结果是 1
为什么,就是我函数定义的时候,作用域就确定了:
foo
定义的位置是在全局作用域- 它的外层作用域是 全局
- 无论
foo
在哪里被调用(哪怕是在bar
里面),它都会在自己定义时的作用域链里去找变量
执行过程拆解
- 编译阶段:
-
- 全局创建变量
value = 1
- 创建函数
foo
(作用域链指向全局作用域) - 创建函数
bar
(作用域链指向全局作用域)
- 全局创建变量
- 执行阶段:
-
- 调用
bar()
- 调用
-
-
bar
内部声明value = 2
(只在bar
的作用域里可见)- 调用
foo()
-
-
-
-
foo
在全局作用域 中定义,所以会从全局作用域找value
- 全局
value
是1
,所以输出1
-
-
如果想输出 2 怎么办?
必须让 foo
在 bar
的作用域里定义,这样它的外层作用域才是 bar
这里要引生出作用域链:
- 每个函数都有一个作用域链,它记录了:
-
- 自己的变量对象(AO/VO)
- 定义时的父作用域
- 一直往上直到全局作用域
- 查找变量时,从当前作用域开始,一层层往父作用域查找(就像链条一样)
词法作用域 + 作用域链 + 闭包
先来看两个例子,来源于《JavaScript权威指南》
第一段 :
csharp
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f(); // 直接调用 f
}
checkscope(); // => "local scope"
第二段 :
csharp
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f; // 返回函数 f 本身
}
checkscope()(); // => "local scope"
来思考一下,为何两段代码输出是一样的?
原因:词法作用域(Lexical Scope)
- 函数
f
定义时 就已经决定了它能访问的作用域链 - 无论你是在
checkscope
里直接调用f()
(第一段)
还是返回f
再调用f()
(第二段) - 它查找变量
scope
时,都会先找自己作用域 → 找不到就去定义时的父作用域 (checkscope
)里找 - 在
checkscope
作用域里,scope = "local scope"
,所以结果都是"local scope"
那它们有什么不同?
第一段:普通函数调用
f
在checkscope
执行时被直接调用checkscope
执行结束后,局部变量scope
会被销毁(没有引用了)
执行过程:
scss
checkscope()
├── 创建局部变量 scope = "local scope"
├── 定义 f(),作用域链:[f作用域, checkscope作用域, 全局作用域]
├── 调用 f() → 找到 checkscope 作用域的 scope
└── checkscope 返回结果 "local scope"
第二段:闭包
checkscope
返回f
本身,f
被外部变量引用- 因为
f
依然可以访问checkscope
里的变量scope
,checkscope 的作用域不会被销毁(闭包现象)
scss
checkscope()
├── 创建局部变量 scope = "local scope"
├── 定义 f(),作用域链:[f作用域, checkscope作用域, 全局作用域]
├── 返回 f(没有调用)
↓
调用返回的 f()
├── 从自己的作用域链找到 checkscope 的 scope
└── 返回 "local scope"
结果一样,是因为词法作用域决定了 f
的作用域链,查找 scope
时都会访问到 checkscope
的 scope
变量;
不同点在于第一段没有闭包,执行结束变量就销毁,而第二段形成闭包,变量会被保留下来。