【问题标题】:Why does instanceof evaluate to true here?为什么 instanceof 在这里评估为真?
【发布时间】:2017-08-19 14:28:10
【问题描述】:

在这个 sn-p 中,语句 f instanceof PipeWritable 返回 true (Node v8.4.0):

const stream = require('stream');
const fs = require('fs');

class PipeWritable extends stream.Writable {
    constructor () {
        super();
    }
}

const s = new PipeWritable();
const f = fs.createWriteStream('/tmp/test');

console.log(f instanceof PipeWritable); // true ... ???

对象s

  • Object.getPrototypeOf(s)PipeWritable {}
  • s.constructor[Function: PipeWritable]
  • PipeWritable.prototypePipeWritable {}

对象f:

  • Object.getPrototypeOf(f)WriteStream { ... }
  • f.constructor[Function: WriteStream] ...
  • stream.WriteStream.prototypeWritable { ... }

原型链

Object f                    Object s
---------------------       --------------------
  Writable                    PipeWritable
    Stream                      Writable
      EventEmitter                Stream
        Object                      EventEmitter
                                      Object

关注definition of instanceof

instanceof 运算符测试其原型链中的对象是否具有构造函数的原型属性。

我希望(f instanceof PipeWritable) === false,因为PipeWritable 不在f 的原型链中(上面的链通过Object.getPrototypeOf(...) 的调用验证)。
但它返回true,因此我的分析有问题。

正确答案是什么?

【问题讨论】:

  • 这真是太奇怪了。
  • 其他类也会出现这种情况吗?也许只是 Streams 或 Nodejs 错误的一些奇怪之处?
  • FWIW,我可以在 Node v8.2.1 上验证您的结果。 “我希望 (f instanceof PipeWritable) === false,因为构造函数 PipeWritable 不在 f 的原型链中。” 这不是 函数链,它是PipeWritable.prototype 指向的对象。但这也不在链中。 (我检查了。)
  • 扩展 stream.Readable 不会产生同样的错误,顺便说一句
  • 看起来可能与此有关:github.com/nodejs/node/pull/8834(节点 6.7.0 返回 false,节点 6.8.0 返回 true

标签: javascript node.js ecmascript-6


【解决方案1】:

这是由于 Node.js 源代码中的某个部分代码,在 _stream_writable.js

var realHasInstance;
if (typeof Symbol === 'function' && Symbol.hasInstance) {
  realHasInstance = Function.prototype[Symbol.hasInstance];
  Object.defineProperty(Writable, Symbol.hasInstance, {
    value: function(object) {
      if (realHasInstance.call(this, object))
        return true;

      return object && object._writableState instanceof WritableState;
    }
  });
} else {
  realHasInstance = function(object) {
    return object instanceof this;
  };
}

通过language specificationinstanceof 运算符使用众所周知的符号@@hasInstance 来检查对象 O 是否是构造函数 C 的实例:

12.9.4 运行时语义:InstanceofOperator(O, C)

抽象操作InstanceofOperator(O, C)实现了判断一个对象O是否继承自构造函数C定义的继承路径的通用算法时间>。此抽象操作执行以下步骤:

  1. 如果Type(C) 不是 Object,则抛出 TypeError 异常。
  2. instOfHandlerGetMethod(C,@@hasInstance)。
  3. ReturnIfAbrupt(instOfHandler).
  4. 如果 instOfHandler 不是 undefined,则
    一种。返回ToBoolean(Call(instOfHandler, C, «O»))。
  5. 如果 IsCallable(C) 为 false,则抛出 TypeError 异常。
  6. 返回OrdinaryHasInstance(C, O)。

现在让我为你逐段分解上面的代码:

var realHasInstance;
if (typeof Symbol === 'function' && Symbol.hasInstance) {
  …
} else {
  …
}

上面的 sn -p 定义了realHasInstance,检查Symbol是否被定义,以及众所周知的符号hasInstance是否存在。在您的情况下,确实如此,因此我们将忽略 else 分支。下一个:

realHasInstance = Function.prototype[Symbol.hasInstance];

这里,realHasInstance 被分配给Function.prototype[@@hasInstance]

19.2.3.6 Function.prototype[@@hasInstance] (V)

当对象F的@@hasInstance方法以V的值被调用时,会采取以下步骤:

  1. Fthis 值。
  2. 返回OrdinaryHasInstance(F, V)。

Function@@hasInstance 方法只是调用 OrdinaryHasInstance。下一个:

Object.defineProperty(Writable, Symbol.hasInstance, {
  value: function(object) {
    if (realHasInstance.call(this, object))
      return true;

    return object && object._writableState instanceof WritableState;
  }
});

这在Writable 构造函数上定义了一个新属性,即众所周知的符号hasInstance——实质上实现了它自己的hasInstance 自定义版本。 hasInstance 的值是一个接受一个参数的函数,即由instanceof 测试的对象,在本例中为f

下一行 if 语句检查 realHasInstance.call(this, object) 是否为真。前面提到过,realHasInstance 被赋值给Function.prototype[@@hasInstance],实际上是调用了内部操作OrdinaryHasInstance(C, O)。 OrdinaryHasInstance 操作只是通过在原型链中查找构造函数来检查 O 是否是您和 MDN 描述的 C 的实例。

在这种情况下,Writable f 不是 Writable (PipeWritable) 子类的实例,因此 realHasInstance.call(this, object) 为 false。既然是假的,就转到下一行:

return object && object._writableState instanceof WritableState;

由于object,或在本例中为f,是真实的,并且由于f 是具有_writableState 属性的可写对象,该属性是WritableState 的实例,f instanceof PipeWritabletrue


这个实现的原因在comments:

// Test _writableState for inheritance to account for Duplex streams,
// whose prototype chain only points to Readable.

因为双工流在技术上是可写的,但它们的原型链只指向可读,额外检查_writableState 是否是WritableState 的实例允许duplexInstance instanceof Writable 为真。这有一个您发现的副作用 - Writable 是“子类的实例”。这是一个错误,应该报告。

这实际上甚至在documentation中有所报道:

注意:stream.Duplex 类原型继承自 stream.Readable 并寄生自 stream.Writable,但由于在 stream.Writable 上覆盖 Symbol.hasInstanceinstanceof 将适用于这两个基类。

从 Writable 寄生继承会有一些后果,如下所示。


我提交了issue on GitHub,它看起来会得到修复。作为Bergi mentioned,添加一个检查以查看this === Writable,确保在使用instanceof 时只有双工流是可写的实例。有一个pull request

【讨论】:

  • 好的,非常棘手。所以如果我想检查一个对象是否是用new PipeWritable创建的,我必须检查构造函数s.constructor === PipeWritable而不是使用instanceof。在documentation 中添加一些提示会很有用。我在documentation - Implementing a Duplex Stream 上找到了 hasInstance 主题,但很难看到 Writable 孩子的后果。
  • @ManfredSteiner 是的,检查对象上的constructor 它的原型链是 instanceof 的替代方案,它完全适用于此。
  • @AndrewLi 我认为Writable[Symbol.hasInstance] 方法需要this == Writable && … 检查。
  • @ManfredSteiner 最好使用 PipeWritable.prototype.isPrototypeOf(s) 而不是构造函数检查,以考虑子类。
猜你喜欢
  • 2020-04-03
  • 1970-01-01
  • 2014-05-19
  • 2014-02-16
  • 1970-01-01
  • 2018-07-01
  • 2017-04-28
  • 2023-03-20
  • 1970-01-01
相关资源
最近更新 更多