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

时间:2017-08-19 14:28:10

标签: javascript node.js ecmascript-6

在此代码段中,语句f instanceof PipeWritable返回true(节点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运算符测试其原型链中的对象是否具有构造函数的prototype属性。

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

答案是什么?

1 个答案:

答案 0 :(得分:15)

这是由于_stream_writable.js中Node.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.   
  3. instOfHandler GetMethod C ,@@ hasInstance)。
  4.   
  5. ReturnIfAbrupt instOfHandler )。
  6.   
  7. 如果 instOfHandler 不是未定义,那么
      一个。返回ToBooleanCall instOfHandler C «O»))。
  8.   
  9. 如果IsCallable C false ,则抛出 TypeError 例外。
  10.   
  11. 返回OrdinaryHasInstance C O )。
  12.   

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

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

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

realHasInstance = Function.prototype[Symbol.hasInstance];

此处,realHasInstance已分配给Function.prototype[@@hasInstance]

  

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

     

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

     
      
  1. F 值。
  2.   
  3. 返回OrdinaryHasInstance F V )。
  4.   

@@hasInstance的{​​{1}}方法只调用OrdinaryHasInstance。下一个:

Function

这在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

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

在这种情况下,Writable Function.prototype[@@hasInstance]不是Writable(f)子类的实例,因此PipeWritable为false。由于这是错误的,它会转到下一行:

realHasInstance.call(this, object)

由于return object && object._writableState instanceof WritableState; object在这种情况下是真实的,因为f是一个带有f属性的Writable,它是{{1}的一个实例},_writableState true

此实施的原因在于comments

WritableState

由于Duplex流在技术上是可写的,但它们的原型链仅指向可读,因此额外检查以查看f instanceof PipeWritable// Test _writableState for inheritance to account for Duplex streams, // whose prototype chain only points to Readable. 的实例是否允许_writableState为真。这有你发现的副作用 - Writable是'子类的一个实例'。这是一个错误,应该报告。

实际上甚至在documentation

中报告了这一点
  

注意:WritableState类原型继承自duplexInstance instanceof Writable并寄生于stream.Duplex,但由于覆盖Symbol.hasInstancestream.Readable将适用于两个基类在stream.Writable

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

我提交了issue on GitHub,看起来它已经修好了。在Bergi mentioned时,添加一项检查以查看是否instanceof,确保在使用stream.Writable时,只有Duplex流是Writable的实例。有pull request