js 执行上下文
前言
什么执行上下文?
就是当前代码将会在什么样的环境运行。
执行上下文分为3类:
1.全局执行上下文,在全局中的上下文
2.函数执行上下文,在函数中的上下文
3.Eval函数执行上下文,很少使用。如果安全最好使用Function。
全局执行上下文:
这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。
它做了两件事:
1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。
2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
在此就不过多介绍全局执行上下文,主要介绍函数执行上下文。
函数执行上下文
每一个函数执行的时候都会有一个执行前,都会有一个函数执行上下文,那么是如何管理的呢?
有一個執行上下文棧:
其实上面这个图不准确。
看到图的main,也就是栈的最底层。显示的应该是globalContext.
执行console.log的时候log的上下文进栈,当要执行bar的时候,bar的上下文进栈,然后far上下文进栈,当far执行完毕,far的上下文出栈。
这样做的好处就是,当执行代码的时候,只要取栈顶,那么得到的就是当前上下文执行的栈。
执行上下文的生命周期:
创建阶段 → 执行阶段 → 回收阶段
创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
1.创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明。下文会详细说明。
2.创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。
作用域链本身包含变量对象。作用域链用于解析变量。
3.当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
确定 this 指向
先来看下变量对象:
- 变量声明提升
console.log(a); // undefined
var a = 10;
简单点就是说,在程序运行之前扫码一遍,把变量进树。然后赋值undefine.
2.函数声明提升
在程序运行之前扫码一遍,把函数进树,注意是将函数对象进树,此时是会实例化的。
注意事项:
当函数申明提示和变量声明提升 冲突的时候,那么函数申明为主
3.确定this
直接给图看哈。
作用域链:
什么是作用域链?是这样子的。
我们发现有一个问题,那就是我们可以访问全局变量,这神奇吗?
可能有些人觉得,这有什么?我们的思维已经固定了,认为不就是访问全局变量吗。
它的实现是这样子的:
上下文时候有一个[[scope]],存储的是上级上下文的变量对象。
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
checkscope.[[scope]] = [
globalContext.VO
];
那么这时候可以回顾一下上下文是如何创建的。
先创建上下文对象,假如checkscope的上下文,我命名为checkscopeContext
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]]]
}
后面进入执行步骤,当函数执行完毕弹出。