本文涵盖了一个高级主题,以便更好地理解某些极端情况。
这并不重要。许多经验丰富的开发人员在不知道的情况下也能很好地生活。如果您想了解事物的内部运作方式,请继续阅读。
动态求值的函数调用可能会丢失 this
。
例如
let user = {
name: "John",
hi() { alert(this.name); },
bye() { alert("Bye"); }
};
user.hi(); // works
// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!
在最后一行有一个条件运算符,它选择 user.hi
或 user.bye
。在这种情况下,结果是 user.hi
。
然后使用括号 ()
立即调用该方法。但它不能正常工作!
如你所见,调用导致错误,因为调用内部的 "this"
值变为 undefined
。
此方法有效(对象点方法)
user.hi();
此方法无效(已评估方法)
(user.name == "John" ? user.hi : user.bye)(); // Error!
为什么?如果我们想要了解为什么发生这种情况,让我们深入了解 obj.method()
调用如何工作的内部原理。
引用类型说明
仔细观察,我们可能会注意到 obj.method()
语句中有两个操作
- 首先,点
'.'
检索属性obj.method
。 - 然后,括号
()
执行它。
那么,有关 this
的信息如何从第一部分传递到第二部分?
如果我们将这些操作放在单独的行上,那么 this
肯定会丢失
let user = {
name: "John",
hi() { alert(this.name); }
};
// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined
此处 hi = user.hi
将函数放入变量中,然后在最后一行中它完全独立,因此没有 this
。
为了使 user.hi()
调用起作用,JavaScript 使用了一个技巧——点 '.'
返回的不是函数,而是特殊 引用类型 的值。
引用类型是一种“规范类型”。我们无法显式使用它,但语言内部使用它。
引用类型的值是三值组合 (base, name, strict)
,其中
base
是对象。name
是属性名称。- 如果
use strict
生效,则strict
为 true。
属性访问 user.hi
的结果不是函数,而是引用类型的值。对于严格模式中的 user.hi
,它为
// Reference Type value
(user, "hi", true)
当在引用类型上调用括号 ()
时,它们会收到有关对象及其方法的完整信息,并且可以设置正确的 this
(在本例中为 user
)。
引用类型是一种特殊的“中间”内部类型,其目的是将信息从点 .
传递到调用括号 ()
。
任何其他操作(如赋值 hi = user.hi
)都会整体丢弃引用类型,获取 user.hi
的值(函数)并将其传递。因此,任何进一步的操作都会“丢失”this
。
因此,结果是,只有在使用点 obj.method()
或方括号 obj['method']()
语法直接调用函数时,才会正确传递 this
的值(它们在此处执行相同操作)。有多种方法可以解决此问题,例如 func.bind()。
摘要
引用类型是语言的内部类型。
读取属性(例如使用点 .
在 obj.method()
中)不会返回属性值,而是返回一个特殊的“引用类型”值,该值存储属性值和从中获取该值的属性。
这是为了后续方法调用 ()
获取对象并将 this
设置为该对象。
对于所有其他操作,引用类型会自动变为属性值(在本例中为函数)。
整个机制对我们来说是隐藏的。它只在一些微妙的情况下很重要,例如使用表达式从对象动态获取方法时。
评论
<code>
标记,对于多行 - 将它们包装在<pre>
标记中,对于超过 10 行 - 使用沙箱(plnkr,jsbin,codepen…)