JS 中 this 突然指向 undefined?可能是因为“Reference type”

592 阅读2分钟

一个动态执行的方法可能会丢失 this。

我们先来看看以下代码

'use strict'
let user = {
  name: "John",
  hi() { alert(this.name); }
}

// 把获取方法和调用方法拆成两行
let hi = user.hi;
hi(); // 报错了,因为 this 的值是 undefined

这里 hi = user.hi 把函数赋值给了一个变量,接下来在最后一行它是完全独立的,所以这里没有 this

为确保 user.hi() 调用正常运行,JavaScript 玩了个小把戏 —— 点 . 返回的不是一个函数,而是一个特殊的 Reference Type 的值。

Reference TypeECMAScript 中的一个“规范类型”。我们不能直接使用它,但它被用在 JavaScript 语言内部。

Reference Type 的值是一个三个值的组合 (base, name, strict),其中:

  • base 是对象。

  • name 是属性名。

  • strictuse strict 模式下为 true

对属性 user.hi 访问的结果不是一个函数,而是一个 Reference Type 的值。对于 user.hi,在严格模式下是:

// Reference Type 的值
(user, "hi", true)

()Reference Type 上调用时,它们会接收到关于对象和对象的方法的完整信息,然后可以设置正确的 this(在此处为 user)。

Reference Type 是一个特殊的“中间人”内部类型,目的是从 . 传递信息给 () 调用。

任何例如赋值 hi = user.hi 等其他的操作,都会将 Reference Type 作为一个整体丢弃掉,而会取 user.hi(一个函数)的值并继续传递。所以任何后续操作都“丢失”了 this

丢失原因

this 的值仅在函数直接被通过点符号 obj.method() 或方括号 obj['method']() 语法(此处它们作用相同)调用时才被正确传递 。

复习代码

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [object Object]

(obj.go)();             // (2) [object Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

setTimeout(obj.go,1000);    //(5) [object Window]

如何避免this指向丢失

  • 使用 bind(),如:修复上行代码(3)(method=obj.go.bind(obj))() //[object Object]
  • 使用一个包装函数,如:修复上行代码(5) setTimeout(()=>obj.go(),1000) // [object Object]

总结

Reference Type 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。JS 代码中存在的基本类型就只有 Undefined, Null, Boolean, String, NumberSymbolBigIntObject。(Array 其实是特殊的 Object

读取一个属性,例如在 obj.method() 中,. 返回的准确来说不是属性的值,而是一个特殊的 “Reference Type” 值,其中储存着属性的值和它的来源对象。

这是为了随后的方法调用 () 获取来源对象,然后将 this 设为它。

对于所有其它操作,Reference Type 会自动变成属性的值(在我们这个情况下是一个函数)。

这整个机制对我们是不可见的。它仅在一些微妙的情况下才重要,例如使用表达式从对象动态地获取一个方法时。

引用: 尤雨溪 | javascriptInfo | ESMA规范