Наследование JavaScript и свойство конструктора - PullRequest
36 голосов
/ 11 ноября 2011

Рассмотрим следующий код.

function a() {}
function b() {}
function c() {}

b.prototype = new a();
c.prototype = new b();

console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
console.log((new c()).constructor); //a()
  • Почему конструктор не обновляется для b и c?
  • Я неправильно наследую?
  • Каков наилучший способ обновить конструктор?

Далее, пожалуйста, учтите следующее.

console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
  • Учитывая, что (new c()).constructor равно a() иObject.getPrototypeOf(new c()) - это a{ }, как instanceof может узнать, что new c() является экземпляром c?

http://jsfiddle.net/ezZr5/

Ответы [ 3 ]

64 голосов
/ 11 ноября 2011

Хорошо, давайте поиграем в небольшую интеллектуальную игру:

На изображении выше мы видим:

  1. Когда мы создаем такую ​​функцию, как function Foo() {}, JavaScript создает экземпляр Function.
  2. Каждый экземпляр Function (функция конструктора) имеет свойство prototype, являющееся указателем.
  3. Свойство prototype функции конструктора указывает на объект-прототип.
  4. Объект-прототип имеет свойство constructor, которое также является указателем.
  5. Свойство constructor объекта-прототипа указывает на его функцию конструктора.
  6. Когда мы создаем новый экземпляр Foo, такой как new Foo(), JavaScript создает новый объект.
  7. Внутреннее свойство [[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 в прототипе выгодно по следующим причинам:

  1. Это логически правильно. Например, рассмотрим Object.prototype. Свойство constructor Object.prototype указывает на Object. Если свойство constructor было определено для экземпляра, тогда Object.prototype.constructor будет undefined, поскольку Object.prototype является экземпляром null.
  2. Он не отличается от других методов-прототипов. Это облегчает работу с new, поскольку не требуется определять свойство constructor для каждого экземпляра.
  3. Каждый экземпляр имеет одно и то же свойство constructor. Следовательно, это эффективно.

Теперь, когда мы говорим о наследовании, мы имеем следующий сценарий:

На изображении выше мы видим:

  1. Свойство prototype производного конструктора установлено на экземпляр базового конструктора.
  2. Следовательно, внутреннее свойство [[proto]] экземпляра производного конструктора также указывает на него.
  3. Таким образом, свойство 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 будет иметь вид:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

Как видите, каждый объект наследуется от конструктора Object. Цепочка прототипов заканчивается, когда внутреннее свойство [[proto]] указывает на null.

Функция instanceof просто обходит цепочку прототипов объекта экземпляра (первый операнд) и сравнивает внутреннее свойство [[proto]] каждого объекта со свойством prototype функции конструктора (второй операнд). Если они совпадают, возвращается true; а если цепочка прототипов заканчивается, она возвращает false.

12 голосов
/ 11 ноября 2011

По умолчанию

function b() {}

затем b.prototype имеет свойство .constructor, которое автоматически устанавливается на b. Однако вы в настоящее время перезаписываете прототип и, таким образом, отбрасываете эту переменную:

b.prototype = new a;

Тогда b.prototype больше не имеет свойства .constructor; он был стерт с перезаписью. Он наследует от a и, следовательно, (new a).constructor === a и, следовательно, (new b).constructor === a (это относится к тому же свойству в цепочке прототипов).

Лучше всего просто установить его вручную:

b.prototype.constructor = b;

Вы также можете сделать небольшую функцию для этого:

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}

http://jsfiddle.net/79xTg/5/

5 голосов
/ 11 ноября 2011

constructor - это обычное не перечисляемое свойство значения по умолчанию свойства prototype функциональных объектов.Таким образом, присвоение prototype приведет к потере свойства.

instanceof все равно будет работать, так как не использует constructor, а скорее сканирует цепочку прототипов объекта на предмет (текущего) значениясвойство prototype функции, т.е. foo instanceof Foo эквивалентно

var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto)) {
    if(proto === Foo.prototype)
        return true;
}
return false;

В ECMAScript3 нет способа установить свойство constructor, которое ведет себя идентично встроенному, так как пользовательские свойствавсегда перечисляемый (т.е. видимый для for..in).

Это изменилось в ECMAScript5.Тем не менее, даже если вы установите constructor вручную, ваш код по-прежнему будет иметь проблемы: в частности, плохой идеей будет установить prototype для экземпляра parent-'class '- родительский конструктор не должен вызываться, когдаchild-'class 'определяется, но, скорее, когда создаются дочерние экземпляры.

Вот пример кода ECMAScript5, как это должно быть сделано:

function Pet(name) {
    this.name = name;
}

Pet.prototype.feed = function(food) {
    return this.name + ' ate ' + food + '.';
};

function Cat(name) {
    Pet.call(this, name);
}

Cat.prototype = Object.create(Pet.prototype, {
    constructor : {
        value : Cat,
        writable : true,
        enumerable : false,
        configurable : true
    }
});

Cat.prototype.caress = function() {
    return this.name + ' purrs.';
};

Если вы застряли сECMAScript3, вам нужно будет использовать пользовательскую clone() функцию вместо Object.create(), и вы не сможете сделать constructor не перечисляемым:

Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;
...