Хорошо, давайте поиграем в небольшую интеллектуальную игру:
На изображении выше мы видим:
- Когда мы создаем такую функцию, как
function Foo() {}
, JavaScript создает экземпляр Function
.
- Каждый экземпляр
Function
(функция конструктора) имеет свойство prototype
, являющееся указателем.
- Свойство
prototype
функции конструктора указывает на объект-прототип.
- Объект-прототип имеет свойство
constructor
, которое также является указателем.
- Свойство
constructor
объекта-прототипа указывает на его функцию конструктора.
- Когда мы создаем новый экземпляр
Foo
, такой как new Foo()
, JavaScript создает новый объект.
- Внутреннее свойство
[[proto]]
экземпляра указывает на прототип конструктора.
Теперь возникает вопрос: почему JavaScript не присоединяет свойство constructor
к экземпляру объекта вместо прототипа. Рассмотрим:
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
var Square = defclass({
constructor: function (side) {
this.side = side;
},
area: function () {
return this.side * this.side;
}
});
var square = new Square(10);
alert(square.area()); // 100
Как видите, свойство constructor
- это просто еще один метод прототипа, такой как area
в приведенном выше примере. Что делает свойство constructor
особенным, так это то, что оно используется для инициализации экземпляра прототипа. В остальном он точно такой же, как и любой другой способ прототипа.
Определение свойства constructor
в прототипе выгодно по следующим причинам:
- Это логически правильно. Например, рассмотрим
Object.prototype
. Свойство constructor
Object.prototype
указывает на Object
. Если свойство constructor
было определено для экземпляра, тогда Object.prototype.constructor
будет undefined
, поскольку Object.prototype
является экземпляром null
.
- Он не отличается от других методов-прототипов. Это облегчает работу с
new
, поскольку не требуется определять свойство constructor
для каждого экземпляра.
- Каждый экземпляр имеет одно и то же свойство
constructor
. Следовательно, это эффективно.
Теперь, когда мы говорим о наследовании, мы имеем следующий сценарий:
На изображении выше мы видим:
- Свойство
prototype
производного конструктора установлено на экземпляр базового конструктора.
- Следовательно, внутреннее свойство
[[proto]]
экземпляра производного конструктора также указывает на него.
- Таким образом, свойство
constructor
экземпляра производного конструктора теперь указывает на базовый конструктор.
Что касается оператора instanceof
, вопреки распространенному мнению, он не зависит от свойства constructor
экземпляра. Как видно из вышесказанного, это приведет к ошибочным результатам.
Оператор instanceof
является двоичным оператором (он имеет два операнда). Он работает с экземпляром объекта и функцией конструктора. Как объясняется в Mozilla Developer Network , он просто делает следующее:
function instanceOf(object, constructor) {
while (object != null) {
if (object == constructor.prototype) { //object is instanceof constructor
return true;
} else if (typeof object == 'xml') { //workaround for XML objects
return constructor.prototype == XML.prototype;
}
object = object.__proto__; //traverse the prototype chain
}
return false; //object is not instanceof constructor
}
Проще говоря, если Foo
наследуется от Bar
, тогда цепочка прототипов для экземпляра Foo
будет иметь вид:
foo.__proto__ === Foo.prototype
foo.__proto__.__proto__ === Bar.prototype
foo.__proto__.__proto__.__proto__ === Object.prototype
foo.__proto__.__proto__.__proto__.__proto__ === null
Как видите, каждый объект наследуется от конструктора Object
. Цепочка прототипов заканчивается, когда внутреннее свойство [[proto]]
указывает на null
.
Функция instanceof
просто обходит цепочку прототипов объекта экземпляра (первый операнд) и сравнивает внутреннее свойство [[proto]]
каждого объекта со свойством prototype
функции конструктора (второй операнд). Если они совпадают, возвращается true
; а если цепочка прототипов заканчивается, она возвращает false
.