考虑这段代码,它使用 (见第五章)来自省一个对象 a1 来推断它的能力:

因为 Foo.prototype(不是 Foo!)在 a1[[Prototype]] 链上(见第五章),instanceof 操作符(使人困惑地)假装告诉我们 a1 是一个 Foo “类”的实例。有了这个知识,我们假定 a1Foo “类”中描述的能力。

当然,这里没有 Foo 类,只有一个普通的函数 Foo,它恰好拥有一个引用指向一个随意的对象(Foo.prototype),而 a1 恰好委托链接至这个对象。通过它的语法,instanceof 假装检查了 a1Foo 之间的关系,但它实际上告诉我们的是 a1Foo.prototype(这个随意被引用的对象)是否有关联。

instanceof 在语义上的混乱(和间接)意味着,要使用以 为基础的自省来查询对象 a1 是否与讨论中的对象有关联,你 不得不 拥有一个持有对这个对象引用的函数 —— 你不能直接查询这两个对象是否有关联。

回想本章前面的抽象 Foo / Bar / b1 例子,我们在这里缩写一下:

  1. Foo.prototype...
  2. function Bar() { /* .. */ }
  3. Bar.prototype = Object.create( Foo.prototype );

可以说,其中有些烂透了。举个例子,直觉上(用类)你可能想说这样的东西 Bar instanceof Foo(因为很容易混淆“实例”的意义认为它包含“继承”),但在 JS 中这不是一个合理的比较。你不得不说 Bar.prototype instanceof Foo

另一个常见,但也许健壮性更差的 类型自省 模式叫“duck typing(鸭子类型)”,比起 instanceof 来许多开发者都倾向于它。这个术语源自一则谚语,“如果它看起来像鸭子,叫起来像鸭子,那么它一定是一只鸭子”。

例如:

  1. if (a1.something) {
  2. a1.something();
  3. }

与其检查 a1 和一个持有可委托的 函数的对象的关系,我们假设 a1.something 测试通过意味着 a1 有能力调用 .something()(不管是直接在 a1 上直接找到方法,还是委托至其他对象)。就其本身而言,这种假设没什么风险。

但是“鸭子类型”常常被扩展用于 除了被测试关于对象能力以外的其他假设,这当然会在测试中引入更多风险(比如脆弱的设计)。

由于种种原因,需要判定任意一个对象引用是否 是一个 Promise,但测试是通过检查对象是否恰好有 then() 函数出现在它上面来完成的。换句话说,如果任何对象 恰好有一个 then() 方法,ES6 的 Promises 将会无条件地假设这个对象 是“thenable” 的,而且因此会期望它按照所有的 Promises 标准行为那样一致地动作。

如果你有任何非 Promise 对象,而却不管因为什么它恰好拥有 then() 方法,你会被强烈建议使它远离 ES6 的 Promise 机制,来避免破坏这种假设。

这个例子清楚地展现了“鸭子类型”的风险。你应当仅在可控的条件下,保守地使用这种方式。

再次将我们的注意力转向本章中出现的 OLOO 风格的代码,类型自省 变得清晰多了。让我们回想(并缩写)本章的 Foo / Bar / b1 的 OLOO 示例:

使用这种 OLOO 方式,我们所拥有的一切都是通过 [[Prototype]] 委托关联起来的普通对象,这是我们可能会用到的大幅简化后的 类型自省

  1. // `Foo` 和 `Bar` 互相的联系
  2. Foo.isPrototypeOf( Bar ); // true
  3. // `b1` 与 `Foo` 和 `Bar` 的联系
  4. Foo.isPrototypeOf( b1 ); // true
  5. Bar.isPrototypeOf( b1 ); // true
  6. Object.getPrototypeOf( b1 ) === Bar; // true

我想可以说这些检查比起前面一组自省检查,极大地减少了复杂性/混乱。又一次,我们看到了在 JavaScript 中 OLOO 要比类风格的编码简单(但有着相同的力量)。