执行上下文
可执行代码(executable code) 的类型有全局代码、函数代码、eval代码
当 JavaScript 执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)
JavaScript 引擎创建执行上下文栈(Execution context stack,ECS)来管理执行上下文
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
函数执行上下文中作用域链和变量对象的创建过程
- 函数创建,保存所有父级变量对象到函数内部属性
[[scope]] - 执行函数,创建函数执行上下文,函数执行上下文被压入执行上下文栈
- 复制函数
[[scope]]属性创建作用域链 - 用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
- 函数激活将活动对象压入作用域链顶端
- 开始执行函数,随着函数的执行,修改 AO 的属性值
- 函数执行完毕,函数上下文从执行上下文栈中弹出
变量对象
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
全局上下文
- 通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性
- this 引用全局对象
- parseInt() 函数
函数上下文
活动对象(activation object, AO)来表示变量对象
执行上下文的代码会分成两个阶段进行处理
- 初始化
- 进入执行上下文;
- 代码执行;
初始化
函数上下文的变量对象初始化只包括 Arguments 对象
进入执行上下文
变量对象包括:函数的所有形参、函数声明、变量声明
函数的所有形参
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
AO = {
arguments: {
0: 1,
length: 1
}
}
函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性替换这个属性
AO = {
a: reference to function c(){},
}
变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
AO = {
a: undefined,
}
代码执行
在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
作用域链
多个执行上下文的变量对象构成的链表就叫做作用域链。
函数创建
函数创建的时候,就会保存所有父级变量对象到函数内部属性 [[scope]]
foo.[[scope]] = [
globalContext.VO
];
函数激活
当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
this
Reference
- base value 属性所在的对象 EnvironmentRecord
- referenced name 属性的名称
- strict reference 严格模式
- GetBase 返回 reference 的 base value
- IsPropertyReference 如果 base value 是一个对象,返回 true
- MemberExpression 函数括号左边的部分
- GetValue 返回对象属性真正的值
判断是否 Reference
- base value 基本类型
- referenced name 字符串属性名
- strict mode 严格模式
如何确定 this 的值
- 计算 MemberExpression 的结果赋值给 ref
- 判断 ref 是不是一个 Reference 类型
- 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
- 如果 ref 是 Reference,并且 base value 是 Environment Record, 那么this 的值为 undefined
- 如果 ref 不是 Reference,那么 this 的值为 undefined
this 绑定规则
| 调用方式 | this 指向 | 示例 |
|---|---|---|
| 默认绑定 | 全局对象 | foo() |
| 隐式绑定 | 调用对象 | obj.foo() |
| 显式绑定 | 指定对象 | foo.call(obj) |
new 绑定 | 新创建对象 | new Foo() |
| 箭头函数 | 词法作用域 | () => this.x |
Function.prototype.newCall = function(context, ...parameter) {
if (typeof context === 'object') {
// null 指向 window
context = context || window
} else {
// 不是对象创建一个对象
context = Object.create(null)
}
let fn = Symbol()
// 函数挂载对象上面
context[fn] = this
// 执行函数
let res = context[fn](...parameter);
// 删除属性
delete context[fn]
return res
}
Function.prototype.newApply = function (context, parameter) {
if (typeof context === "object") {
context = context || window;
} else {
context = Object.create(null);
}
let fn = Symbol();
context[fn] = this;
let res = !parameter ? context.fn() : context[fn](...parameter);
delete context[fn];
return res;
};
Function.prototype.newBind = function (context, ...innerArgs) {
let that = this;
return function (...finnalyArgs) {
return that.call(context, ...innerArgs, ...finnalyArgs);
};
};
闭包
闭包是指那些能够访问自由变量的函数。
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
当执行 data[0] 函数的时候,data[0] 函数的作用域链:
data[0]Context = {
Scope: [AO, 匿名函数 Context.AO, globalContext.VO]
}
匿名函数执行上下文的AO为:
匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}
data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。
作用
- 保存状态
- 实现私有变量
- 模块化与封装
- 回调函数与异步编程
- 高级函数,柯里化、组合
- React Hook
内存泄漏
- 循环引用导致内存泄露
- 创建的定时器未被清理
- 全局变量或静态变量过多导致的内存泄露
- 事件绑定没有及时处理
Chrome 开发者工具中,可以通过 Memory(内存) 面板,使用 Heap Snapshot(堆快照)来查看对象的引用关系
在设计数据结构时,尽量避免互相引用,尤其是大的复杂对象
使用 WeakMap或 WeakSet