为什么instanceof在这里评估为true?

Man*_*ner 18 javascript node.js ecmascript-6

在此代码段中,语句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 ... ???
Run Code Online (Sandbox Code Playgroud)

对象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
Run Code Online (Sandbox Code Playgroud)

遵循instanceof定义:

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

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

什么是正确的答案?

Li3*_*357 15

这是由于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;
  };
}
Run Code Online (Sandbox Code Playgroud)

根据语言规范,instanceof运算符使用众所周知的符号@@hasInstance来检查对象O是否是构造函数C的实例:

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

抽象操作InstanceofOperator(O,C)实现了通用算法,用于确定对象O是否继承自构造函数C定义的继承路径.此抽象操作执行以下步骤:

  1. 如果Type(C)不是Object,则抛出TypeError异常.
  2. instOfHandler成为GetMethod(C,@@ hasInstance).
  3. ReturnIfAbrupt(instOfHandler).
  4. 如果instOfHandler没有未定义,那么
    一个.返回ToBoolean(调用(instOfHandler,C,«O»)).
  5. 如果IsCallable(C)为false,则抛出TypeError异常.
  6. 返回OrdinaryHasInstance(C,O).

现在让我逐节为您分解上面的代码:

var realHasInstance;
if (typeof Symbol === 'function' && Symbol.hasInstance) {
  …
} else {
  …
}
Run Code Online (Sandbox Code Playgroud)

上面的代码段定义realHasInstance,检查是否Symbol已定义以及是否hasInstance存在已知符号.在你的情况下,它确实如此,所以我们将忽略else分支.下一个:

realHasInstance = Function.prototype[Symbol.hasInstance];
Run Code Online (Sandbox Code Playgroud)

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

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

当使用值V调用对象F的@@ hasInstance方法时,将执行以下步骤:

  1. ˚F这个值.
  2. 返回OrdinaryHasInstance(F,V).

只需调用OrdinaryHasInstance 的@@hasInstance方法Function.下一个:

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

    return object && object._writableState instanceof WritableState;
  }
});
Run Code Online (Sandbox Code Playgroud)

这在Writable构造函数上定义了一个新属性,即众所周知的符号hasInstance- 基本上实现了自己的自定义版本hasInstance.值hasInstance是一个函数,它接受一个参数instanceof,在这种情况下,正在测试的对象f.

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

在这种情况下,Writable f不是Writable(PipeWritable)的子类的实例,因此realHasInstance.call(this, object)是false.由于这是错误的,它会转到下一行:

return object && object._writableState instanceof WritableState;
Run Code Online (Sandbox Code Playgroud)

因为object,或者f在这种情况下,是真实的,并且因为f是具有_writableState属性的Writable WritableState,f instanceof PipeWritable真实的.


这个实现的原因在于评论:

// Test _writableState for inheritance to account for Duplex streams,
// whose prototype chain only points to Readable.
Run Code Online (Sandbox Code Playgroud)

因为Duplex流在技术上是可写的,但它们的原型链只指向Readable,所以额外检查是否_writableState是一个WritableState允许duplexInstance instanceof Writable为true 的实例.这有你发现的副作用 - Writable是'子类的一个实例'.这是一个错误,应该报告.

这实际上甚至在文档中报告:

注:stream.Duplex类prototypically从继承stream.Readable和寄生从stream.Writable,但instanceof可以正常工作了,由于覆盖两基类Symbol.hasInstancestream.Writable.

如此处所示,从Writable继承parasitcally会产生后果.


在GitHub上提交了一个问题,看起来它将被修复.正如Bergi所提到的,添加一个检查以查看是否this === Writable确保在使用时只有Duplex流是Writable的实例instanceof.有拉动请求.

  • @AndrewLi我认为`Writable [Symbol.hasInstance]`方法需要`this == Writable && ...`检查. (2认同)