在这段代码中,语句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
Object.getPrototypeOf(s)
is s.constructor
is [Function: PipeWritable]
PipeWritable.prototype
is PipeWritable {}
PipeWritable {}
f
**:**的Object
Object.getPrototypeOf(f)
is WriteStream { ... }
stream.WriteStream.prototype
is Writable { ... }
is WriteStream { ... }
[Function: WriteStream] ...
isWritable { ... }
isWriteStream { ... }
stream.WriteStream.prototype
isWritable { ... }
isWriteStream { ... }[Function: WriteStream] ...
is[Function: WriteStream] ...
原型链:
Object f Object s
--------------------- --------------------
Writable PipeWritable
Stream Writable
EventEmitter Stream
Object EventEmitter
Object
instanceof运算符测试其原型链中的对象是否具有构造函数的原型属性。
我希望是(f instanceof PipeWritable) === false
,因为PipeWritable
不在f
的原型链中(上面的链是通过调用Object.getPrototypeOf(...)
来验证的)。
但是它返回true
,因此在我的分析中有些地方是错误的。
正确答案是什么?
发布于 2017-08-19 23:08:52
这是由于_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定义的继承路径继承的通用算法。该抽象操作执行以下步骤:
如果Object(C)不是Object,则抛出一个未定义的对象(C,@@hasInstance).
a. exception.
现在让我一节一节地为你分解上面的代码:
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调用对象F的@@hasInstance方法时,会执行以下步骤:
设F为value.
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
构造函数上定义了一个新属性,即众所周知的symbol hasInstance
--本质上实现了它自己的定制版本的hasInstance
。hasInstance
的值是一个带有一个参数的函数,该参数是由instanceof
测试的对象,在本例中为f
。
下一行if语句检查realHasInstance.call(this, object)
是否为真。前面提到过,realHasInstance
被分配给Function.prototype[@@hasInstance]
,它实际调用内部操作OrdinaryHasInstance(C, O)。操作OrdinaryHasInstance只是通过查找原型链中的构造函数来检查O是否像您和MDN所描述的那样是C的实例。
在这种情况下,可写的f
不是可写(PipeWritable
)的子类的实例,因此realHasInstance.call(this, object)
为false。因为这是假的,所以它转到下一行:
return object && object._writableState instanceof WritableState;
由于object
(本例中的f
)是真的,并且f
是一个具有_writableState
属性的Writable,该属性是WritableState
的一个实例,因此f instanceof PipeWritable
是true。
此实现的原因是在comments中
// Test _writableState for inheritance to account for Duplex streams,
// whose prototype chain only points to Readable.
因为双工数据流在技术上是可写的,但它们的原型链只指向可读,所以额外检查_writableState
是否是WritableState
的实例允许duplexInstance instanceof Writable
为真。这有一个你发现的副作用--一个可写的是“一个子类的实例”。这是一个错误,应该报告。
实际上,这甚至在documentation中也有报道
注意:
stream.Duplex
类典型地继承自stream.Readable
并寄生于stream.Writable
,但是instanceof
将为这两个基类正常工作,因为它覆盖了stream.Writable
上的Symbol.hasInstance
。
如下所示,从Writable中寄生继承会产生一些后果。
我提交了一个issue on GitHub,它看起来会被修复。作为Bergi mentioned,添加检查以查看this === Writable
,确保在使用instanceof
时只有双工流是可写的实例。有一个pull request。
https://stackoverflow.com/questions/45772705
复制相似问题