代码一旦变多,我们就会尝试写函数、拆文件、拆模块,从而让代码更容易。
将庞大的问题拆分成一个个小问题的思想,叫分治(突然想到皇帝的政策)。
同理,JS 引擎在执行阶段,会将把庞大的执行任务划分成不同的执行上下文,降低执行的复杂度。
执行上下文是啥
执行上下文,简言之,“执行代码的环境”。结合分类、生命周期更易懂些。
执行上下文分为 3 类:
- 全局上下文 —— 全局代码所处的环境,不在函数中的代码都在全局执行上下文中
- 函数上下文 —— 在函数调用时创建的上下文
eval
上下文 —— 笔者不用eval
,读者也别用,问就是没用过~
执行上下文的生命周期:
- 创建阶段 —— 执行上下文的初始化状态,此时一行代码都还没有执行,只是做了一些准备工作(进栈)
- 执行阶段 —— 逐行执行脚本里的代码
- 消失阶段 -- 出栈
全局上下文的生命周期
JS 脚本运行起来之后,会第一时间创建全局上下文
。
全局上下文创建阶段做的事情:
- 创建全局对象(Window/Global)
- 创建 this ,并让它指向全局对象
- 给变量和函数安排内存空间,默认给变量赋值为 undefined;将函数声明放入内存
- 创建作用域链
执行阶段就开始执行代码。
而之前所谓的变量提升
,只是变量的创建过程(在上下文创建阶段完成)和真实赋值过程(在上下文执行阶段完成)的不同步带来的一种错觉。
函数上下文的生命周期
和全局上下文大同小异。
函数上下文在函数调用的时候就会被创建。
- 创建 arguments
- 创建 this ,并让它指向调用对象
- 给变量和函数安排内存空间,默认给变量赋值为 undefined;将函数声明放入内存
- 初始化作用域链
执行阶段就开始执行代码。 执行完毕,就出栈了。
上面说到了初始化作用域链,怎么初始化?
执行上下文栈
JS 引擎在执行过程中,会为我们自动创建” 执行上下文栈 “(也叫调用栈)。
全局上下文执行栈始终在最底层。
调用函数的时候,函数上下文就会进栈,执行完就会出栈,也就是该上下文就结束了。
作用域链的生命周期
参考大神写的深入作用域链 作用域链的生命周期:
- 全局上下文创建阶段,声明函数的时候,就保存了根据词法所生成的作用域链
- 函数调用之后,函数上下文被创建(进栈)
- 函数上下文在创建阶段,会复制这个作用域链,作为自己作用域链的初始化,然后根据环境生成变量对象,将这个变量对象,添加到这个复制的作用域链
- 函数上下文在执行阶段,会将当前作用域链的相关值更新
- 函数执行完,函数上下文出栈,刚刚复制的作用域链失效
至于为什么会有两个作用域链,是因为在函数创建的时候并不能确定最终的作用域的样子,为什么会采用复制的方式而不是直接修改呢?应该是因为函数会被调用很多次吧。
举个例子:
var scope = "global scope";
function checkscope() {
var scope2 = "local scope";
return scope2;
}
checkscope();
全局上下文在创建阶段:
stacks = [globalContext];
checkscope.[[scope]] = [
globalContext.VO
];
执行checkscope()
这行代码的时候:
// checkscopeContext进栈
stacks = [checkscopeContext, globalContext];
checkscopeContext 在创建阶段:
// 先复制
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]]]
}
checkscopeContext 在执行阶段:
// 修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: { length: 0 },
scope2: "local scope"
},
Scope: [AO, [[Scope]]]
};
执行完之后,checkscopeContext 出栈
stacks = [globalContext];
为啥,作用域在嵌套的情况下,外部作用域是不能访问内部作用域的变量呢?
因为当 JS 引擎位于外部上下文的时候,表明内部上下文已经出栈了,其维护的自己的作用域链也消失了,自然访问不到其变量。
理解闭包作用域
闭包里,虽然父作用域在父函数执行完就出栈了,但是,因为父函数执行的时候,闭包函数本身也保存了一份父函数的作用域数据,所以当闭包函数执行的时候,仍可访问到父函数的作用域数据。
先看这个例子,参考大神的深入闭包
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
// checkscope执行完之后,checkscopeContext是肯定出栈的
var foo = checkscope();
// 那么在foo执行的时候,为什么能访问
foo();
checkscope执行完之后,checkscopeContext是肯定出栈的。
那为什么foo执行的时候,还能访问checkscopeContext
?
联系之前的作用域生命周期来理解这个问题:
// 全局作用域在创建阶段:
fContext.[[scope]] = [
checkscopeContext.VO,
globalContext.VO
];
// checkscopeContext在运行阶段:
fContext.[[scope]] = [
// 此处保留了checkscopeContext的AO,也就是保留了其arguments/参数值/变量值/定义的函数
checkscopeContext.AO,
globalContext.VO
];
// fContext在执行阶段:
fContext = {
// 优先访问自己的AO,找不到,仍然可以访问checkscopeContext.AO
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!