Преимущества использования Object.create для наследования

#javascript #inheritance #constructor

#javascript

Вопрос:

Я пытался разобраться в новом Object.create методе, который был представлен в ECMAScript 5.

Обычно, когда я хочу использовать наследование, я делаю что-то вроде этого:

 var Animal = function(name) { this.name = name; }
Animal.prototype.print = function() { console.log(this.name); }

var Dog = function() 
{ 
  return Animal.call(this, 'Dog'); 
}

Dog.prototype = new Animal();
Dog.prototype.bark = function() { console.log('bark'); }
  

Я просто присваиваю вновь созданный объект Animal прототипу Dog, и все работает как по маслу:

 var dog1 = new Dog();
dog1.print(); // prints 'Dog'
dog1.bark(); // prints 'bark'
dog1.name; //prints 'Dog'
  

но люди (без объяснения причин) говорят, что Dog.prototype = new Animal(); это не так, как работает наследование, и что я должен использовать подход Object.create:

 Dog.prototype = Object.create(Animal.prototype);
  

что тоже работает.

В чем польза от использования Object.create или я что-то упускаю?

ОБНОВЛЕНИЕ: Некоторые говорят, что Dog.prototype = Animal.prototype; это тоже может сработать. Итак, теперь я в полном замешательстве

Комментарии:

1. Если мне нужно угадать, вы заметите разницу при последующем расширении прототипа Animal Dog.prototype = new Animal(); . Это расширение не будет унаследовано Dog . Однако здесь я не могу проверить эту теорию.

2. @SimonBoudrias К сожалению, я не нашел ничего полезного в этих вопросах

3. Краткий ответ: запуск Animal конструктора может иметь нежелательные побочные эффекты.

4. С Dog.prototype = new Animal(); прототипом dog получает все свойства экземпляра Animal, а также name свойство. Но с Object.create(Animal.prototype); прототипом получает только свойства прототипа Animal .

5. Просто для записи: вот еще один способ реализации наследования классов, взятый из Backbone.js: var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate;

Ответ №1:

В дальнейшем я предполагаю, что вас интересует только то, почему Object.create предпочтительнее для настройки наследования.

Чтобы понять преимущества, давайте сначала уточним, из чего состоит «класс» в JavaScript. У вас есть две части:

  1. Функция конструктора. Эта функция содержит всю логику для создания экземпляра «класса», т. Е. Специфичного для экземпляра кода.

  2. Объект-прототип. Это объект, от которого наследует экземпляр. Он содержит все методы (и другие свойства), которые должны быть общими для всех экземпляров.

Наследование устанавливает отношение is-a, например, a Dog является an Animal . Как это выражается в терминах функции конструктора и объекта-прототипа?

Очевидно, что у собаки должны быть те же методы, что и у животного, то есть объект- Dog прототип должен каким-то образом включать методы из объекта- Animal прототипа. Есть несколько способов сделать это. Вы часто будете видеть это:

 Dog.prototype = new Animal();
  

Это работает, потому что Animal экземпляр наследует от объекта- Animal прототипа. Но это также подразумевает, что каждый dog наследует от одного конкретного Animal экземпляра. Это кажется немного странным. Разве конкретный код экземпляра не должен выполняться только в функции конструктора? Внезапно код конкретного экземпляра и методы прототипа кажутся смешанными.

На самом деле мы не хотим запускать специфичный для Animal экземпляра код в этот момент, нам нужны только все методы из объекта- Animal прототипа. Это то, что Object.create позволяет нам делать:

 Dog.prototype = Object.create(Animal.prototype);
  

Здесь мы не создаем новый Animal экземпляр, мы получаем только методы-прототипы. Специфичный для экземпляра код выполняется именно там, где он должен быть, внутри конструктора:

 function Dog() { 
   Animal.call(this, 'Dog'); 
}
  

Самым большим преимуществом является то, что Object.create он всегда будет работать. Использование new Animal() работает только в том случае, если конструктор не ожидает никаких аргументов. Представьте, если бы конструктор выглядел так:

 function Animal(name) { 
    this.name = name.toLowerCase();
}
  

Вам всегда нужно передавать строку Animal , иначе вы получите сообщение об ошибке. Что вы передадите, когда сделаете Dog.prototype = new Animal(??); ? На самом деле не имеет значения, какую строку вы передаете, если вы передаете что-то, что, надеюсь, показывает вам, что это плохой дизайн.


Некоторые говорят` что Dog.prototype = Animal.prototype; это тоже может сработать. Итак, теперь я в полном замешательстве

Все, что «добавляет» свойства из Animal.prototype to Dog.prototype , будет «работать». Но решения разного качества. В этом случае здесь у вас возникнет проблема, связанная с тем, что любой метод, к которому вы добавляете Dog.prototype , также будет добавлен Animal.prototype .

Пример:

 Dog.prototype.bark = function() {
    alert('bark');
};
  

Поскольку Dog.prototype === Animal.prototype теперь у всех Animal экземпляров есть метод bark , который, безусловно, не то, что вы хотите.

Object.create (и даже new Animal ) добавьте один уровень косвенности к наследованию, создав новый объект, который наследуется от Animal.prototype , и этот новый объект становится Dog.prototype .


Наследование в ES6

ES6 вводит новый синтаксис для создания функций конструктора и методов прототипов, который выглядит следующим образом:

 class Dog extends Animal {

  bark() {
    alert('bark');
  }

}
  

Это более удобно, чем то, что я объяснил выше, но, как оказалось, extends также использует внутренний эквивалент Object.create для настройки наследования. Смотрите шаги 2 и 3 в проекте ES6.
Это означает, что использование Object.create(SuperClass.prototype) является «более правильным» подходом в ES5.

Комментарии:

1. 1 для «работает только в том случае, если конструктор не ожидает никаких аргументов»

2. Хорошее объяснение — хорошая работа. Но. Если Animal действительно нужно что-то установить в конструкторе, можно поспорить, является ли разрешение Object.create для создания полуфабриката Animal плюсом или минусом. Лично я считаю, что это минус. Я все еще не убежден, что Object.create() настолько замечательный.

3. Обычно я просто передаю переменную и options в свой конструктор, который затем расширяется. Тогда, если бы я захотел new Animal() , но с опциями, это выглядело бы new Animal({ species: 'dog', sound: 'bark' }) так, однако, если вы не используете jQuery, вам нужно будет создать собственную функцию расширения.

4. Является ли хорошей практикой для нас, новых разработчиков JavaScript, продолжать углубляться в создание и совместное использование прототипов объектов с Object. Создать в ES6? Особенно, если мы хотим быть верными поведению делегирования объектов? Феликс, каково ваше мнение?

5. В чем идея Object.create (Animal.prototype)? создание другого нового объекта с помощью Animal.prototype и возврат нового экземпляра объекта как это работает?

Ответ №2:

Во-первых, запуск Animal конструктора может иметь нежелательные побочные эффекты. Рассмотрим это:

 var Animal = function(name) {
    this.name = name;
    Animal.instances.push(this);
};
Animal.instances = [];
  

Эта версия будет отслеживать все созданные экземпляры. Вы не хотите, чтобы ваш Dog.prototype там был записан.

Во-вторых, Dog.prototype = Animal.prototype это плохая идея, поскольку это означало бы, что bark это станет методом Animal .

Комментарии:

1. Если этот конструктор Animal имеет нежелательные побочные эффекты, это проблема с дизайном или кодом, а не с техникой наследования.

Ответ №3:

Я пытаюсь немного проиллюстрировать разницу:

Вот что в основном происходит, когда вы пишете new Animal() :

     //creating a new object
    var res = {};

    //setting the internal [[prototype]] property to the prototype of Animal
    if (typeof Animal.prototype === "object" amp;amp; Animal.prototype !== null) {
        res.__proto__ = Animal.prototype;
    }

    //calling Animal with the new created object as this
    var ret = Animal.apply(res, arguments);

    //returning the result of the Animal call if it is an object
    if (typeof ret === "object" amp;amp; ret !== null) {
        return ret;
    }

    //otherise return the new created object
    return res;
  

И вот что в основном происходит с Object.create :

     //creating a new object
    var res = {};

    //setting the internal [[prototype]] property to the prototype of Animal
    if (typeof Animal.prototype !== "object") {
        throw "....";
    }
    res.__proto__ = Animal.prototype;

    //return the new created object
    return res;
  

Таким образом, он делает то же самое, но не вызывает Animal функцию, а также всегда возвращает новый созданный объект.
В вашем случае вы получаете два разных объекта. С помощью первого метода вы получаете:

 Dog.prototype = {
    name: undefined,
    __proto__: Animal.prototype
};
  

и со вторым методом вы получаете:

 Dog.prototype = {
    __proto__: Animal.prototype
};
  

На самом деле вам не нужно иметь это name свойство в вашем прототипе, потому что вы уже назначили его своему Dog экземпляру с Animal.call(this, 'Dog'); помощью .

Ваша основная цель — предоставить вашему Dog экземпляру доступ ко всем свойствам Animal прототипа, что достигается обоими методами. Однако первый метод выполняет некоторые дополнительные действия, которые на самом деле не нужны в вашем случае или могут даже вызвать нежелательные результаты, как упоминал Pumbaa80.

Ответ №4:

Давайте разберемся в этом только с помощью кода;

A.прототип = B.прототип;

 function B() {console.log("I am B");this.b1= 30;}
    B.prototype.b2 = 40;

    function A() {console.log("I am A");this.a1= 10;}
    A.prototype.a2 = 20;

    A.prototype = B.prototype;

    A.prototype.constructor = A; 

    var a = new A;
    var b = new B;

    console.log(a);//A {a1: 10, b2: 40}
    console.log(b);//B {b1: 30, b2: 40}

    console.log(A.prototype.constructor);//A
    console.log(B.prototype.constructor);//A
    console.log(A.prototype);//A {b2: 40}
    console.log(B.prototype);//A {b2: 40}
    console.log(a.constructor === A); //true
    console.log(b.constructor === A); //true

console.log(a.a2);//undefined
  

введите описание изображения здесь

A.prototype = Object.create(B.prototype);

 function B() {console.log("I am B");this.b1= 30;}
B.prototype.b2 = 40;

function A() {console.log("I am A");this.a1= 10;}
A.prototype.a2 = 20;

A.prototype = Object.create(B.prototype);

A.prototype.constructor = A; 

var a = new A;
var b = new B;

console.log(a);//A {a1: 10, constructor: function, b2: 40}
console.log(b);//B {b1: 30, b2: 40} 

console.log(A.prototype.constructor);//A
console.log(B.prototype.constructor);//B
console.log(A.prototype);//A {constructor: function, b2: 40}
console.log(B.prototype);//B {b2: 40}
console.log(a.constructor === A); //true
console.log(b.constructor === B); //true
console.log(a.a2);//undefined
  

введите описание изображения здесь