在此代码段中,语句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.prototype
是PipeWritable {}
对象f
:
Object.getPrototypeOf(f)
是WriteStream { ... }
f.constructor
是[Function: WriteStream] ...
stream.WriteStream.prototype
是Writable { ... }
原型链:
Object f Object s
--------------------- --------------------
Writable PipeWritable
Stream Writable
EventEmitter Stream
Object EventEmitter
Object
instanceof运算符测试其原型链中的对象是否具有构造函数的prototype属性。
我希望(f instanceof PipeWritable) === false
,因为PipeWritable
不在f
的原型链中(上面的链通过调用Object.getPrototypeOf(...)
来验证)。
但它会返回true
,因此我的分析中出现了问题。
答案是什么?
答案 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 specification,instanceof
运算符使用众所周知的符号@@hasInstance
来检查对象 O 是否是构造函数 C的实例:
12.9.4运行时语义:InstanceofOperator(O,C)
抽象操作 InstanceofOperator(O,C)实现了通用算法,用于确定对象 O 是否继承自构造函数 C定义的继承路径。此抽象操作执行以下步骤:
- 如果Type( C )不是Object,则抛出 TypeError 异常。
- 让 instOfHandler 为GetMethod( C ,@@ hasInstance)。
- ReturnIfAbrupt( instOfHandler )。
- 如果 instOfHandler 不是未定义,那么
一个。返回ToBoolean(Call( instOfHandler , C ,«O»))。- 如果IsCallable( C ) false ,则抛出 TypeError 例外。
- 返回OrdinaryHasInstance( C , O )。
醇>
现在让我分一段为你分解上面的代码:
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方法时,将执行以下步骤:
- 让 F 为此值。
- 返回OrdinaryHasInstance( F , V )。
醇>
@@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.hasInstance
,stream.Readable
将适用于两个基类在stream.Writable
。
如此处所示,从Writable继承parasitcally会产生后果。
我提交了issue on GitHub,看起来它已经修好了。在Bergi mentioned时,添加一项检查以查看是否instanceof
,确保在使用stream.Writable
时,只有Duplex流是Writable的实例。有pull request。