开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
执行上下文
栈:一种数据结构,遵循先进后出,后进先出的规则
执行上下文栈:call stack,所有执行上下文组成的内存空间
执行上下文:分为 全局执行上下文 和 函数执行上下文
- 全局执行上下文
JS
代码执行之前,必须先创建全局执行上下文,也就是说,全局执行上下文必须最先加入执行栈,位于栈底
- 函数执行上下文
一个函数运行之前创建的一块内存空间,空间中包含有该函数执行所需要的数据,为该函数执行提供支持
执行栈顶的执行上下文就是当前正在执行的代码环境
执行上下文中的内容
-
创建变量对象(VO: variable object)(重点)
- 确定所有
形参值
以及特殊变量arguments
- 确定函数中通过var声明的变量,将它们的值设置为
undefined
,如果VO
中已有该名称,则直接忽略
- 确定函数中通过
字面量声明
的函数,将它们的值设置为指向函数对象
,如果VO
中已存在该名称,则覆盖
- 确定所有
当一个上下文中的代码执行的时候,如果上下文中不存在某个属性,则会从之前的上下文寻找。
-
确定 this 指向:略,详见 this指向
-
确定作用域(详见下文)
全局执行上下文中 VO
也叫做 GO
(global object)
VO
可以看作是拥有下列三个属性的一个对象:
- variableObject:
{}
,变量对象,包含arguments
对象,形参、函数和局部变量等 - scopeChain: {},作用域链,包含内部上下文所有变量对象的列表
- this:{},上下文中
this
指向的对象
const foo = function(i){
var a = "Hello";
var b = function privateB(){};
var c;
c = "World";
function c(){}
}
foo(10);
代码执行到 foo(10)
时,先创建变量对象
如下:
fooExecutionContext = {
variavleObject : {
arguments: { 0: 10, length: 1 }, // 确定 arguments
i: 10, // 确定形参
a: undefined, // 确定变量,赋值为undefined
b: undefined, // 确定变量,赋值为undefined
// 确定变量阶段:c 作为变量,赋值为 undefined,
// 确定字面量声明的函数:覆盖 VO 中已经存在的变量c,指向 ƒ c(){}
c: ƒ c(){},
},
scopeChain : {},
this : {}
}
代码执行阶段
,为变量赋值
fooExecutionContext = {
variavleObject : {
arguments: { 0: 10, length: 1 },
i: 10,
a: "Hello",
b: ƒ privateB(){},
c: "World", // 执行到 c = "Wolrd",给 c 重新赋值
},
scopeChain : {},
this : {}
}
小结
在创建变量对象的阶段,字面量声明的函数优先级最高,当前 VO
中存在同名变量时,函数覆盖原变量的值,而 var
声明的变量则会直接忽略当前变量的初始化操作(赋值为 undefined
)
试题
var foo = 1;
function bar(a) {
console.log(a); // ?1
var a1 = a;
var a = foo;
function a() {
console.log(a); // ?2
}
a1();
}
bar(3);
// 全局上下文 创建阶段
globalExecutionContext: {
variableObject: {
foo: undefined // 确定变量
}
}
// 执行阶段
globalExecutionContext: {
variableObject: {
foo: 1 // 确定变量
}
}
bar(3)
// bar函数上下文 创建阶段
barExecutionContext: {
variableObject: {
arguments: { 0: 3, length: 1 }, // 确定 arguments
// 先确定形参 a: 3
// 再确定var 声明的 变量 a,由于当前 VO 中已经存在变量 a, 所以不会再赋值为undefined(被忽略)
// 最后确定字面量声明的函数a,当前VO存在变量a,将其覆盖为 ƒ a(){...}
a: ƒ a(){...},
a1: undefined,
}
}
// bar函数上下文 执行阶段
barExecutionContext: {
variableObject: {
arguments: { 0: 3, length: 1 },
a: 1, // 赋值为foo,当前 VO 中不存在,会从之前的上下文中寻找
a1: ƒ a(){...}, // a1 被赋值为创建阶段的 a(先执行的 a1 = a,再对 a 进行赋值)
}
}
通过上面的示例分析可以知道: 问号1
输出的 变量a
的值为 ƒ a(){...}
; 问号2
输出的 变量a
的值为 1
。
作用域及作用域链
VO
中包含一个额外的属性,该属性指向创建该VO
的函数本身- 每个函数在创建时,会有一个隐藏属性
[[scope]]
,它指向创建该函数时的VO
- 当访问一个变量时,会先查找自身
VO
中是否存在,如果不存在,则依次查找[[scope]]
属性
代码示例:
var g = 0;
function A() {
var a = 1;
function B() {
var b = 2;
console.log(b, a, g); // 2 1 0
}
B();
}
A();
图示:
通过上图可以知道:在函数B的执行上下文中访问b,a,g三个变量时,分别会:
- 访问
b
,查找当前VO
,存在变量b
,直接使用 - 访问
a
,查找当前VO
,不存在变量a
,查找函数B
的[[scope]]
即函数A
的VO
,存在变量a
,直接使用 - 访问
g
,查找当前VO
,不存在变量a
,查找函数B
的[[scope]]
即函数A
的VO
,不存在变量g
,继续查找函数A
的[[scope]]
即全局上下文
的GO
,存在变量g
,直接使用