Skip to content

Latest commit

 

History

History
114 lines (71 loc) · 4.31 KB

File metadata and controls

114 lines (71 loc) · 4.31 KB

Reference 类型

为了理解某种特定的边界情况,这篇文章涉及到了一个高级主题。

没关系,好多有经验的开发者即使不知道这部分知识,也能做好工作。但如果你想了解它们是如何运行的,就读下去吧。

动态求值的方法调用可能丢失 this

例如:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // 正常

// 现在,让我们根据 name 的值来调用 user.hi 或者 user.bye
*!*
(user.name == "John" ? user.hi : user.bye)(); // Error!
*/!*

在最后一行,有一个三元运算符来选择 user.hi 或者 user.bye的值。在这种情况下,结果是 user.hi

之后,这个方法被括号 ()立即调用。但它并没有正确运行!

正如你所看到的,调用的结果是一个 error ,因为在调用中 this 的值变成了 undefined

第七行代码运行正常 (以对象名加点的形式调用):

user.hi();

而这一行就不行 (被赋值的方法):

(user.name == "John" ? user.hi : user.bye)(); // Error!

为什么? 如果我们想了解到底发生了什么, 就让我们深入了解一下 obj.method() 调用的工作原理.

Reference 类型的解释

仔细观察,我们观察到在 obj.method() 声明中有两步运算:

  1. 首先,点运算符 '.' 检索到对象属性 obj.method
  2. 然后,括号运算符 () 执行它。

所以,有关 this 的信息如何从第一步传递到第二步的呢?

假如我们把这些运算分成两行进行,那么 this 就会丢失:

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

*!*
// 把获取和调用方法分成两行
let hi = user.hi;
hi(); // Error, 因为 this 值为 undefined
*/!*

这里的 hi = user.hi 把函数传递给变量,而后,最后一行的执行是完全独立的,所以这里没有 this 值。

为了让 user.hi() 工作正常,JavaScript 使用了一种技巧——点运算符 '.'。它的返回值不是一个函数,而是一种特殊的 引用类型

引用类型是一种 "规范类型"。我们不能显式地使用它, 它被使用在语言内部。

引用类型的值是一种三个值的组合 (base, name, strict)

  • base 是一个对象
  • name 表示一个属性名(String 或者 Symbol 类型)
  • strict 标识符,如果它的值为真,则代表使用严格模式(use strict)。

访问一个属性 user.hi 的结果不是一个函数,而是引用类型的值. For user.hi in strict mode it is:

// 引用类型的值如下
(user, "hi", true)

当括号运算符 () 在引用类型后被使用, 他就接收到了对象及其方法得完整信息,并且设置正确的 this(在这种情况下是 =user)。

引用类型是一种特殊的「中介」内部类型,它的作用是从点运算符 . 到括号调用 () 传递信息。

任何其他的运算,例如赋值运算 hi = user.hi,会整体丢弃引用类型,而只取属性的值 user.hi (在这里是一个函数) 并传递下去,所以,在这之后,任何进一步的操作都会「丢失」this.

因此,this 的值只会在函数被点运算符 obj.method() 或中括号语法 obj['method']() 直接调用时正确传递(在这里它们做了相同的事)。在本教程的后面,我们会学习用多种方法解决这个问题,例如 func.bind().

总结

引用类型 Referance Type 是一种语言的内部类型。

当读取一个属性的时候,例如 obj.method()这个语句中的点运算符 . ,返回的并不完全是属性值,而是一个特殊的「引用类型」值,这个值存储了属性值和属性值所属的对象。

这是为了接下来的方法调用 () 能够得到对象并正确的设置 this 值。

对于其他所有的操作,引用类型会自动转换为属性值(在我们的例子中是一个函数)。

这一套原理我们肉眼不可见。它只在一些微妙的情况下起作用,比如使用表达式从对象动态获取方法时。

点运算符 . 的结果事实上并不是一个方法,而是一种 需要某种方法去传递 obj 的信息 的值。