执行栈和执行上下文
运行 JavaScript 代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域, 确定this,创建局部变量对象等
JavaScript 中执行环境
- 全局环境
- 函数环境
- eval 函数环境
那么与之对应的执行上下文类型同样有 3 种:
- 全局执行上下文
- 函数执行上下文
- eval 函数执行上下文
执行栈(函数调用栈)
栈:先进后出
程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。
因为 JavaScript 在执行代码时最先进入全局环境,所以处于栈底的永远是全局环境的执行上下文。而处于栈顶的是当前正在执行函数的执行上下文。
function foo () {
function bar () {
return 'I am bar';
}
return bar();
}
foo();
执行上下文和 this 之间存在密切的关系,因为 this 的值取决于当前执行的上下文。
在 JavaScript 中,每个函数都有自己的执行上下文,并且 this 的值是在运行时确定的,取决于函数的调用方式。具体来说:
- 在全局执行上下文中,this 的值通常指向全局对象(在浏览器环境下通常是 window 对象)。
- 在函数执行上下文中,this 的值取决于函数的调用方式:
-
- 如果函数被作为对象的方法调用,this 就指向该对象。
- 如果函数被独立调用,this 则指向全局对象或 undefined(在严格模式下)。
- 如果使用 call、apply 或 bind 方法来调用函数,可以显式指定 this 的值。
因此,执行上下文和 this 是紧密相关的,了解当前执行的上下文可以帮助确定 this 的值。
执行上下文生命周期
执行上下文的生命周期有两个阶段:
- 创建阶段(进入执行上下文):函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段。
- 执行阶段(代码执行):执行函数中代码时,此时执行上下文进入执行阶段。
创建阶段
创建阶段要做的事情主要如下:
- 创建变量对象(VO:variable object)
-
- 确定函数的形参(并赋值)
- 函数环境会初始化创建 Arguments对象(并赋值)
- 确定普通字面量形式的函数声明(并赋值)
- 变量声明,函数表达式声明(未赋值)
- 确定 this 指向(this ****由调用者确定)
- 确定作用域(词法环境决定,哪里声明定义,就在哪里确定)
当处于执行上下文的建立阶段时,我们可以将整个上下文环境看作是一个对象。该对象拥有 3 个属性,如下
executionContextObj = {
variableObject : {}, // 变量对象,里面包含 Arguments 对象,形式参数,函数和局部变量
scopeChain : {},// 作用域链,包含内部上下文所有变量对象的列表
this : {}// 上下文中 this 的指向对象
}
可以看到,这里执行上下文抽象成为了一个对象,拥有 3 个属性,分别是变量对象,作用域链以及 this 指向,这里我们重点来看一下变量对象里面所拥有的东西。
1.在函数的建立阶段
-
- 首先会建立 Arguments 对象
- 然后确定形式参数
- 检查当前上下文中的函数声明,每找到一个函数声明,就在 variableObject 下面用函数名建立一个属性,属性值就指向该函数在内存中的地址的一个引用。
2.如果上述函数名已经存在于 variableObject(简称 VO) 下面,那么对应的属性值会被新的引用给覆盖。
3.对变量和函数表达式进行声明,如果遇到和函数名同名的变量,则会忽略此操作。
1.全局上下文
2.给foo变量赋值
3.调用foo(生成一个foo的函数上下文环境)
const foo = function(i){
var a = "Hello";
var b = function privateB(){};
function c(){}
}
foo(10);
创建阶段的变量对象如下:
fooExecutionContext = {
variavleObject : {
arguments : {0 : 10,length : 1}, // 确定 Arguments 对象
i : 10, // 确定形式参数
c : pointer to function c(), // 确定函数引用
a : undefined, // 局部变量 初始值为 undefined
b : undefined // 局部变量 初始值为 undefined
},
scopeChain : {},
this : {}
}
执行阶段
fooExecutionContext = {
variavleObject : {
arguments : {0 : 10,length : 1},
i : 10,
c : pointer to function c(),
a : "Hello",// a 变量被赋值为 Hello
b : pointer to function privateB() // b 变量被赋值为 privateB() 函数
},
scopeChain : {},
this : {}
}
在进行变量声明的时候,如果发现该变量名已经存在,则不会再声明
(function () {
console.log(typeof foo);
console.log(typeof bar);
var foo = "Hello";
var bar = function () {
return "World";
}
function foo() {
return "good";
}
console.log(foo, typeof foo);
})()
foo不会再声明成undefined
fooExecutionContext = {
variavleObject : {
arguments : {length : 0},
foo : pointer to function foo(),
bar : undefined
},
scopeChain : {},
this : {}
}