Ответ 1
Хорошо, пусть играет в игру с ума:
Из приведенного выше изображения видно:
- Когда мы создаем функцию типа
function Foo() {}
, JavaScript создает экземплярFunction
. - Каждый экземпляр
Function
(функция-конструктор) имеет свойствоprototype
, которое является указателем. - Свойство
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
.