Почему в JavaScript делают как Object instanceof Function
, так и Function instanceof Object
return true
?
Я попробовал его в Safari WebInspector.
Почему в JavaScript делают как Object instanceof Function
, так и Function instanceof Object
return true
?
Я попробовал его в Safari WebInspector.
Мне потребовалось время, чтобы разобраться, но это действительно стоит потраченного времени. Сначала давайте посмотрим, как работает instanceof
.
Цитата из MDN,
Оператор
instanceof
проверяет, имеет ли объект в своей цепочке прототипов свойствоprototype
конструктора.
[instanceof]
Теперь давайте посмотрим, как instanceof
определяется спецификацией ECMA 5.1,
Произведение
RelationalExpression: RelationalExpression instanceof ShiftExpression
оценивается следующим образом:
- Пусть
lref
будет результатом оценкиRelationalExpression
.- Пусть
lval
beGetValue(lref)
.- Пусть
rref
будет результатом оценкиShiftExpression
.- Пусть
rval
beGetValue(rref)
.- Если
Type(rval)
не является объектом, бросьте исключениеTypeError
.- Если
rval
не имеет внутреннего метода[[HasInstance]]
, бросьте исключениеTypeError
.- Возвращает результат вызова внутреннего метода
[[HasInstance]]
rval
с аргументомlval
.
Сначала вычисляются выражения слева и справа (GetValue
), а результат правой стороны должен быть объектом с внутренним методом [[HasInstance]]
. Не все объекты имеют внутренний метод [[HasInstance]]
, но функции. Например, следующее будет терпеть неудачу
console.log(Object instanceof {});
# TypeError: Expecting a function in instanceof check, but got #<Object>
[[HasInstance]]
Теперь давайте посмотрим, как [[HasInstance]]
был определен в спецификации ECMA 5.1,
Предположим, что
F
является объектом Function.Когда вызывается внутренний метод
[[HasInstance]]
F
со значениемV
, выполняются следующие шаги:
- Если
V
не является объектом, вернитеfalse
.- Пусть
O
является результатом вызова внутреннего метода[[Get]]
F
с именем свойства"prototype"
.- Если
Type(O)
не является объектом, бросьте исключениеTypeError
.- Повторите
- Пусть
V
будет значением внутреннего свойства[[Prototype]]
V
.- Если
V
-null
, вернитеfalse
.- Если
O
иV
относятся к одному и тому же объекту, вернитеtrue
.
Это так просто. Возьмите свойство prototype
F
и сравните его с внутренним свойством [[Prototype]]
O
до тех пор, пока он не станет null
или prototype
of F
не будет таким же, как O
.
[[Prototype]]
внутреннее свойствоСначала давайте посмотрим, что является внутренним свойством [[Prototype]]
,
Все объекты имеют внутреннее свойство
[[Prototype]]
. Значение этого свойства либоnull
, либо объект и используется для реализации наследования. Может ли собственный объект иметь объект-хост, поскольку его[[Prototype]]
зависит от реализации. Каждая цепочка[[Prototype]]
должна иметь конечную длину (то есть, начиная с любого объекта, рекурсивный доступ к внутреннему свойству[[Prototype]]
должен в конечном итоге привести к значениюnull
).
Примечание. Мы можем получить это внутреннее свойство с помощью функции Object.getPrototypeOf
.
prototype
свойство [[HasInstance]]
также говорит о другом свойстве prototype
, которое относится к объектам Function
.
Значение свойства
prototype
используется для инициализации внутреннего свойства[[Prototype]]
для вновь созданного объекта до того, как объект Function вызывается как конструктор для этого вновь созданного объекта.
Это означает, что когда объект функции используется как конструктор, будет создан новый объект, и новый объект будет иметь свой внутренний [[Prototype]]
, инициализированный этим свойством prototype
. Например,
function Test() {}
Test.prototype.print = console.log;
console.log(Object.getPrototypeOf(new Test()) === Test.prototype);
# true
Теперь вернемся к актуальному вопросу. Возьмем первый случай
console.log(Object instanceof Function);
# true
Сначала он выберет Function.prototype
, и он попытается найти, находится ли этот объект в иерархии прототипов Object
. Посмотрим, как получится
console.log(Function.prototype);
# [Function: Empty]
console.log(Object.getPrototypeOf(Object));
# [Function: Empty]
console.log(Object.getPrototypeOf(Object) === Function.prototype);
# true
Так как Function.prototype
соответствует внутреннему свойству Object
[[Prototype]]
, он возвращает true
.
Теперь давайте возьмем второй случай
console.log(Function instanceof Object);
# true
console.log(Object.prototype);
# {}
console.log(Object.getPrototypeOf(Function));
# [Function: Empty]
console.log(Object.getPrototypeOf(Function) === Object.prototype);
# false
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Function)));
# {}
Object.getPrototypeOf(Object.getPrototypeOf(Function)) === Object.prototype
# true
Здесь мы сначала получаем Object.prototype
, который равен {}
. Теперь он пытается найти, существует ли тот же объект {}
в цепочке прототипов Function
. Непосредственный родительский элемент Function
является и пустой функцией.
console.log(Object.getPrototypeOf(Function));
# [Function: Empty]
Это не то же самое, что Object.prototype
console.log(Object.getPrototypeOf(Function) === Object.prototype);
# false
Но алгоритм [[HasInstance]]
не останавливается на достигнутом. Он повторяет и поднимает еще один уровень
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Function)));
# {}
И это то же самое, что и Object.prototype
. Вот почему это возвращает true
.
Из MDN:
Оператор instanceof проверяет, имеет ли объект в своей прототипной цепочке свойство прототипа конструктора.
По существу, он проверяет, есть ли Object
(а не экземпляр Object
, но сам конструктор) имеет экземпляр Function.constructor
где-то вверх по цепочке прототипов.
И действительно:
> Function.__proto__.__proto__ === Object.prototype
true
> Object.__proto__ === Function.prototype
true
Это объясняет, почему Object instanceof Function
, а также наоборот.
ВСЕ объекты имеют внутреннее свойство, называемое [[Prototype]]. Значение этого свойства равно либо null, либо объекту и используется для реализации наследования. Если вы попытаетесь найти ключ на объекте и его не найти, JavaScript будет искать его в цепочке прототипов.
Конструктор Function создает новые объекты Function (экземпляры конструктора Function). Свойство prototype специфично для объектов Function. Конструктор Function сам является объектом Function (экземпляр конструктора Function).
Когда объект Function используется как конструктор, будет создан новый объект, и новый объект будет иметь свой [[Прототип]], инициализированный с помощью свойства prototype конструктора.
function Dog () {}
var myCrazyDog = new Dog();
myCrazyDog.__proto__ === Dog.prototype // true
Спецификация языка состоит в том, что все объекты являются экземплярами конструктора Object, а все функции являются экземплярами конструктора Function.
Object instanceof Функция истинна, потому что Object является функцией и, следовательно, является экземпляром Function (Object является объектом Function - экземпляром конструктора Function). Объект наследуется от Function.prototype.
console.log(Object instanceof Function) // true
console.log(Object.__proto__ === Function.prototype) // true
Объект instanceof Object имеет значение true, потому что Object наследует функцию Function.prototype. Поскольку Function.prototype является объектом, он наследует Object.prototype. Экземпляр функции объекта true, потому что функция наследует функцию Function.prototype. Поскольку Function.prototype является объектом, он наследует Object.prototype. Цепочка прототипа выглядит так:
Object ---> Function.prototype ---> Object.prototype ---> null
Function ---> Function.prototype ---> Object.prototype ---> null
console.log(Object instanceof Object) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(Object.__proto__.__proto__ === Object.prototype) // true
console.log(Function instanceof Object) // true
console.log(Function.__proto__ === Function.prototype) // true
console.log(Function.__proto__.__proto__ === Object.prototype) // true
Функция instanceof Function истинна. Функция - это сам экземпляр (естественно, поскольку его функция и, следовательно, экземпляр функции). Цепочка прототипа выглядит так:
Function ---> Function.prototype ---> Object.prototype ---> null
console.log(Function instanceof Function) // true
console.log(Function.__proto__ === Function.prototype) // true
console.log(Function.__proto__.__proto__ === Object.prototype) // true
Поэтому имейте в виду, что конструкторы Function() и Object() являются функциями. Поскольку они являются функциями, они являются экземплярами конструктора Function() и наследуются от Function.prototype. Поскольку Function.prototype является объектом, Function.prototype является экземпляром Object, таким образом, наследуется от Object.prototype.
console.log(Object instance of Function) // true
console.log(Function instance of Function) // true
console.log(Function.prototype instanceof Object); // true
Источником путаницы в вашем вопросе является внутренняя двойственная природа функций * в JavaScript (ECMAScript).
Функции в js являются одновременно регулярными функциями и объектами. Подумайте о них как алгоритмическом Dr. Джекил и мистер Хайд. Они выглядят как объекты снаружи, но внутри они просто ваши старые добрые js-функции со всеми их причудами, или, может быть, наоборот.
JavaScript действительно сложный бизнес:)
Итак, вернемся к вашему вопросу, заимствуя синтаксис, появляющийся на MDN:
object instanceof constructor
Применяя его к первому выражению в вашем коде:
Object instanceof Function
Здесь у вас есть Object
, функция-конструктор, которая используется как инициализатор объекта, но поскольку функции приводят к двойному проживанию в js, у него есть привязанные к объекту реквизиты и методы, связанные с ним, что также эффективно отражается на объекте.
Итак, первое условие в инструкции выполнено. Мы продолжаем исследовать другое условие или операнд.
Function
, как вы могли заметить, также является конструктором функций, но его другая сторона объекта сейчас не интересует нас во время выполнения этого конкретного оператора.
Итак, синтаксические условия встречаются как "объект" и "конструктор". Теперь мы можем приступить к расследованию их наследственного отношения и если есть связь между ними.
Так как Object
является самой рабочей функцией, имеет смысл предположить, что у нее есть внутренняя прототипная поддержка, указывающая на ссылку на объект Function.prototype
, поскольку в функциях js ALL наследуется их реквизита и методов через это же место Function.prototype
.
true
определенно является ТОЛЬКО ожидаемым результатом этого сравнения, выполняемого оператором instanceof
.
В другом случае:
Function instanceof Object
Так как мы уже установили, что функции из js также имеют объектную сторону. Имеет смысл, что они получили свои фантастические игрушки, специфичные для объекта, из Object.prototype
, и поэтому они представляют собой экземпляры конструктора Object.
Надеюсь, я не добавил к путанице с моими объяснениями и аллегориями.:)
*: Не только функции, которые приводят к двойной жизни в js. Почти все типы данных в js имеют темную сторону объекта, которая облегчает выполнение операций и манипуляций без каких-либо проблем.
Самое плохое свойство на самом деле состоит в том, что функция является экземпляром самого себя. Function instanceof Function
возвращает true.
Это хорошо объяснено в Kannan Удивительно элегантная модель типа Javascript, http://web.archive.org/web/20140205182624/http://vijayan.ca/blog/2012/02/21/javascript-type-model
Цитата в конце объяснения:
Да, это означает, что функция является экземпляром самого себя (естественно, поскольку его функция и, следовательно, экземпляр функции). Это то, с чем мы все время занимались, сознательно или нет, уже давно - все функции-конструкторы являются регулярными функциями и, следовательно, экземплярами Function, а сама функция - это просто функция-конструктор для построения других функций, поэтому она тоже является экземпляром функции.