作用域
作用域:程序源代码定义变量区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript采用词法作用域(lexical scoping),也就是静态作用域。
静态作用域与动态作用域
因为JavaScript采用的是词法作用域,函数的作用域在函数定义的时候就决定了
。而与词法作用域相对
的是动态作用域,函数的作用域是在函数调用的时候才决定的。
一个简单示例,思考下输出结果?
- 假设JavaScript
采用静态作用域
:执行foo函数,先查找内部
是否有局部变量value,如果没有,则根据代码书写位置,查找上一层代码
,也就是value=1,结果=1。 - 假设JavaScript
采用动态作用域
:执行foo函数,查找内部
是否有局部变量value,如果没有,就从调用函数的作用域,也就是bar函数内部查找
,所以结果=2.
思考题
《JavaScript权威指南》中的例子
两段代码各自的执行结果是多少?
原文地址
作用域链
当查找变量的时候,会先从当前上下文
的变量对象中查找,如果没有找到,就会从父级执行上下文
中查找,一直找到全局对象
。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
下面,以函数创建和激活来说明作用域链的创建和变化
函数创建
之前有说道,函数的作用域在函数定义的时候就决定了
。这是因为函数内部有一个属性[[scope]]
,是虚拟出来的一个属性,我们实际访问时访问不到这个属性的。当函数创建的时候,就会保存所有父级变量对象到其中,但是注意[[scope]]并不代表完整的作用域链!
比如我们创建这样一个函数
function foo() {
function bar() {
...
}
}
函数创建时,对应的[[scope]]
为
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
思考题
函数激活
结合变量对象和执行上下文,查看函数执行上下文中作用域链和变量对象的创建过程。
以此为例
- 1,checkscope 函数被创建,保存作用域链到 内部属性
[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
- 2,.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
- 3,checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数
[[scope]]
属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
- 4,第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
- 5,第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
- 6,准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
- 7,查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];