作用域、作用域链与执行上下文栈入门
前置知识
变量对象(Variable Object)
用来放这个执行上下文中可被访问 但是不能被delete的函数、标示符、形参等等。
活动对象(active Object)
活动对象就是作用域链上正在被执行和引用的变量对象。
执行上下文
当执行到一个函数时,就会进行一个准备工作,这里的准备工作用专业名词将就是执行上下文,
(准备工作包括在执行栈中产生一个变量对象,这个执行对象我们并不可以直接使用,它包括变量对象,this ,作用域链)
执行上下文栈
我们写的函数多了去了,如何管理创建的那么多执行上下文呢?
所以 JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文
为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:
ECStack = [];
试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以 ECStack 最底部永远有个 globalContext:
ECStack = [
globalContext
];
现在 JavaScript 遇到下面的这段代码了:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了这样的工作原理,让我们来看看如何处理上面这段代码:
// 伪代码
// fun1()
ECStack.push(<fun1> functionContext);
// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext);
// 擦,fun2还调用了fun3!
ECStack.push(<fun3> functionContext);
// fun3执行完毕
ECStack.pop();
// fun2执行完毕
ECStack.pop();
// fun1执行完毕
ECStack.pop();
// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext
作用域
程序代码中定义变量的区域
作用域 | 词法作用域 | 动态作用域 |
---|---|---|
定义 | 函数定义时就决定了 | 函数调用时就决定了 |
语言 | js | bash |
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()
作用域链
答:当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
函数创建: 函数内部有一个属性scope当函数创建时这个属性就已经存在里面保存着所用父级变量
函数激活:会创建该函数自己的AO对象并添加到这个作用链的前端
总结一下函数执行上下文中作用域链和变量对象的创建过程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
执行过程如下:
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
}
}
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
];