1. 执行上下文(Execution Context)
执行上下文是 JavaScript 中代码执行的环境的抽象概念,它包含了代码运行时所需的所有信息,比如变量的值、函数的引用等。
每当 JavaScript 代码执行前,都会创建一个执行上下文,并将其添加到执行栈中。执行上下文负责代码的执行和管理作用域。
JavaScript 中有三种执行上下文类型(环境)。
- 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。他会执行两件事,创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。
- 函数执行上下文:每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤。
- Eval 函数执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文。
执行上下文包含三个重要的属性:
- 变量对象(Variable Object):包含变量、函数声明和形参等。
- 作用域链(Scope Chain):用于解析标识符的链表。
- this 指向:指向函数执行时的上下文对象。
JavaScript 运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行下文。
由于代码中会声明多个函数,对应的函数执行上下文也会存在多个。在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。
const foo = function (i) {console.log(b) // undefinedconsole.log(c) // [Function: c]let a = 'hello'let b = function privateB() {}function c(){}
}
foo(10)// 1. 创建上下文阶段// vo 里面确定的东西:
// 1. 函数的形参(并赋值)
// 2. 函数的 arguments 对象(并赋值)
// 3. 确定普通字面量形式的函数声明(并赋值)
// 3. 变量声明、函数表达式声明(未赋值)// 将执行上下文看做是一个对象
// fooExecutionContext = {// vo = {// i: 10,// arguments: {0: 10, length: 1},// c: 指向 c 那个函数,// a: undefined,// b: undefined// },// this,// scope
// }// 2. 执行代码阶段
fooExecutionContext = {vo = {i: 10,arguments: {0: 10, length: 1},c: 指向 c 那个函数,a: 'hello',b: privateB},this,scope
}
2. 栈(Stack)
执行栈是一个后进先出(LIFO)的数据结构,用于存储执行上下文。当 JavaScript 代码执行时,会创建执行上下文,并将其推入执行栈的顶部。当函数执行完成后,对应的执行上下文将从栈顶弹出,控制权返回到上一个执行上下文。
执行栈(函数调用栈)
理解完栈的存取方式,我们接着分析 JavaScript 中如何通过栈来管理多个执行上下文。
程序执行会先创建全局执行上下文,所以栈底都是全局执行上下文;而栈顶是正在执行函数的执行上下文。
执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
- 如果栈占用的空间比分配给它的空间还大,那么则会导致“栈溢出”错误。
3. 执行上下文生命周期
执行上下文的生命周期包括以下几个阶段:
- 创建阶段(Creation Phase):在此阶段,JavaScript 引擎会创建执行上下文。在全局上下文中,这一阶段发生在代码执行前;在函数上下文中,这一阶段发生在函数被调用时。
- 初始化变量对象(Variable Object / VO)
- 确定函数形参并赋值
- 函数环境初始化创建arguments对象并赋值
- 确定普通字面量形式的函数声明并赋值
- 变量声明,函数表达式声明(未赋值)
- 建立作用域链(Scope Chain)(在声明定义的地方确定)
- 确定 this 的指向(this 由调用者确定)。
- 初始化变量对象(Variable Object / VO)
- 执行阶段(Execution Phase):在此阶段,JavaScript 引擎会按照代码的顺序执行相应的代码,并为变量赋值、函数调用等。在执行阶段中,JavaScript 引擎会根据作用域链解析变量和函数标识符,并执行相应的代码逻辑。
- 销毁阶段(Deletion Phase):在函数执行完成后或全局代码执行完成后,对应的执行上下文会被销毁。JavaScript 引擎会从执行栈中弹出该执行上下文,并释放相关的内存资源。
(function () {console.log(typeof foo);console.log(typeof bar);var foo = "Hello";var bar = function A() {return "World";}function foo() {return "good";}console.log(foo, typeof foo);
})()
// 1. 创建上下文阶段
// exeutionContext = {
// vo: {
// foo: 指向 foo 函数,
// // 已有 foo 不会创建 foo 变量了
// bar: undefined
// },
// this,
// scope
// }
// 2. 执行代码
exeutionContext = {vo: {foo: "Hello",// 已有 foo 不会创建 foo 变量了bar: function A},this,scope
}console.log(typeof foo); // function
console.log(typeof bar); // undefined
var foo = "Hello";
var bar = function A() {return "World";
}function foo() {return "good";
}console.log(foo, typeof foo); //hello, string