作用域和作用域链是JavaScript中最基础的点,但是也是比较难的点,那么我们就来系统的讲解一下:
作用域:作用域就是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是当前执行代码对变量的访问权限。
JavaScript采用的是词法作用域,也就是静态作用域。也就是函数的作用域在函数定于的时候就决定了。而与静态作用域相对的是动态作用域,只有在函数被调用的时候才决定的。
那么如何查找变量呢?这就是所谓的作用域链。
作用域链:当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
我们来看个例子:
function foo () {
function bar () {
// do something
}
}
我们知道JavaScript是静态作用域,在函数的作用域是在函数被定义的时候决定的。这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
当函数被激活后,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。这时候执行上下文的作用域链,我们命名为 Scope:
Scope = [AO].concat([[Scope]]);
至此,作用域链创建完毕。
下面以一个例子来总结一下整个过程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
(1)checkscope函数创建,将作用域链保存到函数内部的[[scope]]中
checkscope.[[scope]] = [
globalContext.VO
];
(2)执行checkscope,创建checkscope函数的执行上下文,并且压入到执行上下文栈中
ECStack = [
checkscopeContext,
globalContext
];
(3)checkscope并不会立即执行:开始准备工作:
(一)复制函数[[scope]]属性创建作用域链
checkscopeContext = { Scope: checkscope.[[scope]], }
(二)用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: checkscope.[[scope]], }
(三)将活动对象压入 checkscope 作用域链顶端
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }
(4)准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
(5)查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
以上就是作用域和作用域链的相关内容。