一、基础知识
1.执行上下文
执行引擎
为我们的可执行代码块提供了执行前的必要准备工作,这个 “准备工作”,就叫做 “执行上下文(execution context 简称 EC)” 或者也可以叫做执行环境
。
要注意区分函数表达式
与函数声明
:
每次当 JS 引擎解析到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境
,它会形成一个作用域
。JavaScript中的运行环境大概包括三种情况。
- 全局执行上下文 — 由浏览器创建。这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。所有通过var定义的全局变量和函数都会成为window对象的属性和方法。
全局执行上下文中所做的工作:
1. 变量、函数表达式——变量```声明```,默认赋值为undefined;
2.this——赋值
3.函数声明——赋值
复制代码
- 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。
函数执行上下文中所做的工作:
1. 变量、函数表达式——变量```声明```,默认赋值为undefined;
2.this——赋值
3.函数声明——赋值
4.实参——赋值
5.arguments(函数的参数数组,内置属性)——赋值
6.自由变量的取值作用域——赋值
复制代码
- Eval 函数执行上下文 — 不建议使用eval函数
3.执行上下文栈
执行上下文栈也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性(想象成一个羽毛球桶)。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被 调用
(注意:函数被调用时才会将函数执行上下文创建并压入栈中),都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
3.1执行上下文栈的执行
1.全局执行上下文一般由浏览器创建,代码执行时就会创建;函数执行上下文只有函数被调用时才会创建,调用多少次函数就会创建多少上下文。
function f1() {
f2()
console.log(1);
}
function f2() {
f3()
console.log(2);
}
function f3() {
console.log(3);
}
f1() // 3 2 1
复制代码
所以上述代码在代码刚开始执行时就由浏览器创建了一个全局上下文,压入执行栈;在执行最后一行代码–调用f1函数时,创建f1函数上下文,压入执行栈,执行f1函数上下文,在其中调用了f2函数;于是创建f2函数执行上下文,压入执行栈,执行f2函数上下文,在其中调用了f3函数上下文;于是创建f3函数上下文,执行f3函数上下文,输出3,f3上下文执行完毕弹出执行栈;继续执行f2函数上下文,输出2,f2函数上下文执行完毕弹出执行栈;继续执行f1函数上下文,输出1,f1上下文执行完毕弹出执行栈。
3.2执行上下文栈的创建
3.1.1 绑定this
3.1.2 创建词法环境组件(LexicalEnvironment)
3.1.3 创建变量环境组件(VariableEnvironment)
我们通过一串伪代码来理解它们:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
复制代码
//全局执行上下文
GlobalExectionContext = {
// this绑定为全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
//环境记录
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 let const创建的变量a b在这
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
// 全局环境外部环境引入为null
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 var创建的c在这
c: undefined,
}
// 全局环境外部环境引入为null
outer: <null>
}
}
// 函数执行上下文
FunctionExectionContext = {
//由于函数是默认调用 this绑定同样是全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 arguments对象在这
Arguments: {0: 20, 1: 30, length: 2},
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 var创建的g在这
g: undefined
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
}
}
复制代码
在执行上下文创建阶段,函数声明与var声明的变量在创建阶段已经被赋予了一个值,var声明被设置为了undefined,函数被设置为了自身函数,而let const被设置为未初始化。
现在你总知道变量提升与函数声明提前是怎么回事了吧,以及为什么let const为什么有暂时性死域,这是因为作用域创建阶段JS引擎对两者初始化赋值不同。
2.this
在函数中this到底取何值,是在函数真正被调用
执行的时候确定的,函数定义的时候确定不了。因为this的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境。