跳到主要内容

引用类型的规范语义

  • 口径: 本文的"引用类型"是规范内部类型 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 与手写实现;
  • 何时需要关心: 只有在方法被表达式动态取出、或把方法当回调传递时才暴露出来;