# JS-作用域和作用域链
🐴
# 前言
我们在《JS-执行上下文》中已经讨论过执行上下文
,并且提到在执行代码执行中,执行上下文
会初始化三个很重要的对象(变量对象
、作用域链
、this
),这里我们讲解其中作用域链
。
# 词法作用域和动态作用域
在这里我们首先需要很确定的指明的是:
重要
JavaScript
中的语法采用的是词法作用域(lexical scoping),也被称为静态作用域。词法作用域和动态作用域的概念并不是JavaScript
所特有的。
下面我们来了解一下词法作用域和动态作用域:
词法作用域
:词法作用域是在词法分析阶段就确定了,之后不会改变。比如在js中,函数在书写时就应经确定好了作用域。动态作用域
:动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。比如在函数执行时才会确定好作用域。
我们来看看下面的代码:
var name = "King";
function fun(){
console.log(name);
}
function test(){
var name = "Waring";
fun()
}
test()
上面console.log(name)
打印的结果是King
,而不是Waring
,这很说明了在JavaScript
中使用的是词法作用域
。函数fun
在创建时就应经确定了作用域,运行时不会再改变。
而假如JavaScript使用的动态作用域,那么上面console.log(name)
打印的结果是Waring
。
我们再来看一个例子:
var a = "global"
function fun(){
var a = "fun scope";
function f(){
console.log(a)
}
return f()
}
fun()
var a = "global"
function fun(){
var a = "fun scope";
function f(){
console.log(a)
}
return f
}
fun()()
上面结果都会是fun scope
, 这也是因为JavaScript
使用的词法作用域的结果。我们可以打印一个函数,会看到它有一个[[Scope]]
属性,该属存储着这个函数的父级的变量对象。
function fun(){}
console.dir(fun)
打印结果如下图:

# 作用域链
我们知道了JavaScript
中是使用的词法作用域来确定的作用域。在JavaScript
中一个函数是可以访问到他父函数的作用域内的变量的。而这种访问机制就是通过作用域链来完成的。
function fun(){
var a = "fun scope";
function f(){
console.log(a) // 可以访问fun函数内的变量
}
return f()
}
fun()
上面例子,当函数f
中在查找变量a
时,会先从函数f
的活动对象中去查找,如果没有找到会向父级函数fun
的活动对象中去查找a
。如果还没有找到将会继续向上查找,查找全局对象中是否有变量a
。这种层级的变量对象查找就形成了一条链就是作用域链。
# 函数的生命周期
函数分为两个声明周期:创建和激活。
我们来看下面简单的例子:
var str1 = "Hello";
function fun(){
var str2 = "world";
console.log(str1 + str2)
}
上面的例子我们在全局声明了变量str1
,并创建了函数fun
,在程序初始化时他们都将成为全局对象的属性(全局变量对象的属性,全局变量对象等于全局对象)
函数fun
在被创建时,就创建了作用域,也就是拥有了[[Scope]]
属性,该属是父级的层级链,并存储了父级上下文中的变量对象
// 伪代码
VO(globalContext) = {
// 其他属性,对象,函数
str1:undefined,
fun:<fun>
}
fun[[Scope]] = {
0: VO(globalContext)
}
在函数fun
激活时(执行时),函数fun
的活动对象会被创建
// 伪代码
AO(funContext) = {
arguments:Arguments,
str2:undefined,
}
此时fun的作用域链就会被创建好:
scope(fun) = AO(funContext) + [[Scopes]]
// fun函数的作用域链将会变成下面这样
funContext.scope = [
AO(funContext),
VO(globalContext),
]
此时fun
的活动对象将会放到作用域的最前端。这时函数fun
中的代码开始执行,当代码执行到str1 + str2
时,从scope
中的最前端开始查找变量str1
和 变量str2
,如果找到将会拿到某个变量的值并停止查找,如果未找到,将通过作用域链继续向后去查找。
# 影响作用域的代码
当使用with
或catch
时,会修改作用域链。他们会被添加到作用域链的最前端:
var a = 10;
var obj = {
a:1,
}
with(obj){
console.log(a) // 1
var a = 2;
console.log(a) // 2
}
此时作用域链就是这样:
Scope = AO(withContext) + obj + [[Scope]]
← JS-变量对象 JS-This(一) →