Ответ 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;
};
}
В спецификация языка оператор instanceof
использует известный символ @@hasInstance
, чтобы проверить, является ли объект O экземпляром конструктор C:
12.9.4 Семантика выполнения: экземпляр Оператора (O, C)
Реферат Операция InstanceofOperator (O, C) реализует общий алгоритм для определения того, наследует ли объект O путь наследования, определенный конструктором C. Эта абстрактная операция выполняет следующие шаги:
- Если Type (C) не является объектом, бросьте исключение TypeError.
- Пусть instOfHandler будет GetMethod (C, @@hasInstance).
- ReturnIfAbrupt (instOfHandler).
- Если instOfHandler не undefined, тогда а. Верните 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)
Когда метод @@hasInstance объекта F вызывается со значением V, выполняются следующие шаги:
- Пусть F - значение this.
- Вернуть OrdinaryHasInstance (F, V).
Метод @@hasInstance
Function
просто вызывает 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 экземпляром C, как описано вами и MDN, ища конструктор в цепочке прототипов.
В этом случае Writable f
не является экземпляром подкласса Writable (PipeWritable
), поэтому realHasInstance.call(this, object)
является ложным. Поскольку это значение false, оно переходит к следующей строке:
return object && object._writableState instanceof WritableState;
Так как object
или f
в этом случае является правдивым, а поскольку f
является Writable с свойством _writableState
, являющимся экземпляром WritableState
, f instanceof PipeWritable
является true.
Причиной этой реализации является комментарии:
// Test _writableState for inheritance to account for Duplex streams,
// whose prototype chain only points to Readable.
Поскольку дуплексные потоки являются технически Writables, но их прототипные цепочки указывают только на Readable, дополнительная проверка, чтобы увидеть, является ли _writableState
экземпляром WritableState
, для duplexInstance instanceof Writable
значение true. У этого есть побочный эффект, который вы обнаружили - писаемое существо - это экземпляр дочернего класса. Это ошибка, и ее следует сообщить.
Об этом даже сообщается в документации:
Примечание. Класс
stream.Duplex
прототипно наследует отstream.Readable
и паразитно отstream.Writable
, ноinstanceof
будет корректно работать для обоих базовых классов из-за переопределенияSymbol.hasInstance
вstream.Writable
.
Есть последствия для наследования паразитно от Writable, как показано здесь.
Я отправил вопрос на GitHub, и похоже, что он будет исправлен. Как Берги упомянул, добавив проверку, чтобы увидеть, если this === Writable
, убедившись, что только Дуплексные потоки были экземплярами Writable при использовании instanceof
. Там тянуть запрос.