引用类型的规范语义
- 口径: 本文的"引用类型"是规范内部类型 Reference Record, 不是
Object/Array这类引用类型值, 后者见 引用类型;
Reference Record 是什么?为什么要有它?
- 定位: 规范类型 (specification type), 代码里写不出来, 只有引擎内部在用;
- 组成: 三元组
(base, name, strict);base: 属性取自哪个对象;name: 属性名;strict: 是否处于严格模式;
- 职责: 当中间人, 把"这个属性是从谁身上取的"从
.传给后面的();
// 严格模式下 user.hi 求值得到的不是函数, 而是
(user, "hi", true);
- 动机:
obj.method()本质是两步 ——.取属性,()调用; 只传一个函数值无法知道this该是谁;
obj.method() 是怎样把 this 传进去的?
- 第一步:
user.hi求值结果不是函数, 而是带 base 的 Reference Record; - 第二步:
()收到完整引用信息, 于是能把this设为base(这里是user);
const user = {
name: "John",
hi() {
alert(this.name);
},
};
user.hi(); // John, this === user
- 等价写法:
obj.method()与obj['method']()都会保留引用, 效果相同;
为什么拆成两行调用就丢了 this?
const user = {
name: "John",
hi() {
alert(this.name);
},
};
const hi = user.hi;
hi(); // TypeError: this is undefined
- 机制: 赋值属于"其他操作", 会把 Reference Record 整个丢掉, 只留下属性值 (那个函数);
- 结果: 函数独立存在, 不再携带 base,
this变成undefined; - 同源例子: 用条件表达式选出的方法也走同一条路;
(user.name === "John" ? user.hi : user.bye)(); // Error!
除直接调用外, 还有哪些写法会丢掉引用?
- 规则: 只有"取完立刻用
()调用"的形式保留引用; 其他操作一律退化成属性值; - 推论 (常见写法对照):
| 写法 | this 结果 |
|---|---|
user.hi() | user, 引用完整 |
const f = user.hi; f() | undefined, 赋值丢了引用 |
(user.hi)() | user, 括号只是分组, 不构成新操作 |
(0, user.hi)() | undefined, 逗号运算符丢了引用 |
[user.hi].pop()() | undefined, 经数组存取后丢了引用 |
user.hi.call(user) | user, 手动把 base 补回来 |
- 无关操作:
delete user.hi、'hi' in user根本不读取属性值, 只操作属性本身, 不涉及引用; - 判断依据: 问"这个函数是从对象上直接取出并立刻调用的吗", 是则保留, 否则丢失;
遇到 this 丢失该怎么修?
- 箭头函数包一层:
() => user.hi(), 保留原始调用形式; - 绑定:
user.hi.bind(user), 见 call、apply、bind 与手写实现; - 何时需要关心: 只有在方法被表达式动态取出、或把方法当回调传递时才暴露出来;