Спадкування Javascript: викликати суперконструктор або використовувати ланцюжок прототипів?


82

Зовсім недавно я читав про використання дзвінків JavaScript у MDC

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

одне посилання прикладу, показаного нижче, я досі не розумію.

Чому вони тут використовують спадщину так?

Prod_dept.prototype = new Product();

це необхідно? Оскільки в. Є виклик суперконструктору

Prod_dept()

так чи інакше, ось так

Product.call

це просто із загальної поведінки? Коли краще використовувати виклик для суперконструктора або використовувати ланцюжок прототипів?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

Дякуємо, що зрозуміли все


Те, як ви його використовували, майже правильне, але, можливо, ви захочете використовувати Object.create () замість створення екземпляра бази за допомогою нового ключового слова (може спричинити проблеми, якщо конструктору бази потрібні аргументи). У моєму щоденнику є подробиці: ncombo.wordpress.com/2013/07/11/…
Джон,

2
Також зверніть увагу, що Product () ефективно викликається двічі.
event_jr

Відповіді:


109

Відповідь на справжнє питання полягає в тому, що вам потрібно зробити обидва:

  • Встановлення прототипу для екземпляра батьківського файлу ініціалізує ланцюжок прототипів (успадкування), це робиться лише один раз (оскільки об’єкт прототипу є спільним).
  • Виклик батьківського конструктора ініціалізує сам об'єкт, це робиться з кожним екземпляром (ви можете передавати різні параметри щоразу, коли його будуєте).

Тому не слід викликати батьківський конструктор під час налаштування успадкування. Тільки під час створення об'єкта, який успадковується від іншого.

Відповідь Кріса Моргана майже повна, бракує дрібних деталей (властивість конструктора). Дозвольте мені запропонувати метод налаштування успадкування.

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

Дивіться в моєму дописі в блозі, щоб дізнатись про ще більше синтаксичного цукру при створенні класів. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Техніка скопійована з Ext-JS та http://www.uselesspickles.com/class_library/ та коментар з https://stackoverflow.com/users/1397311/ccnokes


6
В EcmaScript5 + (усі сучасні браузери) ви можете зробити його незліченним, якщо визначити його таким Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); чином. Таким чином ви отримаєте точно таку ж "поведінку", як коли javascript створює новий екземпляр функції (конструктор встановлюється як перелічуваний = false false)
Адаптабі,

2
Чи не могли б ви просто спростити метод розширення до двох рядків? А саме: sub.prototype = Object.create (base.prototype); sub.prototype.constructor = під;
Appetere

@ Steve Так, ви можете, коли я вперше писав це, Object.createне так добре підтримувався ... оновлення. Зверніть увагу, що більшість Object.createполіфайлів реалізуються за допомогою техніки, яку я показав спочатку.
Хуан Мендес,

1
Отже, якби я хотів додати методи лише до дочірнього об’єкта та його екземплярів, об’єкта «Собака», у цьому випадку, ви б просто об’єднали два прототипи у своїй функції розширення, як це: jsfiddle.net/ccnokes/75f9P ?
ccnokes

1
@ elad.chen Підхід, який я описав у ланцюгах відповідей прототипу, міксини зазвичай копіюють усі властивості до екземпляра, а не до прототипу. Дивіться stackoverflow.com/questions/7506210/…
Хуан Мендес

30

Ідеальний спосіб це зробити - не робити Prod_dept.prototype = new Product();, оскільки це викликає Productконструктор. Отже, ідеальний спосіб - це клонувати його, за винятком конструктора, приблизно так:

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

Тоді під час побудови викликається суперконструктор, що саме вам потрібно, оскільки тоді ви також можете передавати параметри.

Якщо ви подивитесь на такі речі, як Бібліотека закриття Google, то побачите, як вони це роблять.


Я називаю цей конструктор, який використовується для встановлення успадкування, сурогатним конструктором. Ваш приклад все ще забуває скинути властивість конструктора після налаштування успадкування, щоб ви могли правильно виявити конструктор
Хуан Мендес,

1
@Juan: Добре, оновлено, щоб додати Prod_dept.prototype.constructor = Prod_dept;.
Chris Morgan,

@ChrisMorgan У мене виникли проблеми з розумінням останнього рядка в зразку: Prod_dept.prototype.constructor = Prod_dept;. Перш за все, навіщо це потрібно і чому вказує Prod_deptзамість цього Product?
Lasse Christiansen

1
@ LasseChristiansen-sw_lasse: Prod_dept.prototypeце те, що буде використано як прототип вихідних даних new Prod_dept(). (Зазвичай цей прототип доступний як instance.__proto__, хоча це деталь реалізації.) Що стосується того, чому constructor- це стандартна частина мови, і тому його слід забезпечити для узгодженості; за замовчуванням це правильно, але оскільки ми повністю замінюємо прототип, ми повинні призначити правильне значення ще раз, інакше деякі речі не будуть розумними (у цьому випадку це означало б, що Prod_deptекземпляр мав би this.constructor == Product, що погано).
Chris Morgan

6

Якщо ви зробили об’єктно-орієнтоване програмування в JavaScript, ви будете знати, що можете створити клас наступним чином:

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

Поки що наш класний чоловік має лише дві властивості, і ми збираємось надати йому деякі методи. Чистий спосіб зробити це - використовувати об’єкт „прототип”. Починаючи з JavaScript 1.1, об'єкт-прототип був представлений в JavaScript. Це вбудований об’єкт, який спрощує процес додавання власних властивостей та методів до всіх екземплярів об’єкта. Давайте додамо 2 методи до нашого класу, використовуючи його об'єкт 'prototype' наступним чином:

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

Тепер ми визначили наш клас Людина. Що, якби ми хотіли визначити інший клас під назвою Manager, який успадковує деякі властивості від Person. Немає сенсу перевизначати всі ці властивості знову, коли ми визначаємо наш клас Manager, ми можемо просто встановити його для успадкування від класу Person. JavaScript не має вбудованого успадкування, але ми можемо використовувати техніку для реалізації успадкування наступним чином:

Inheritance_Manager = {};// Створюємо клас менеджера спадщини (ім'я довільне)

Тепер давайте нашому класу успадкування метод, який називається exte, який приймає аргументи baseClass та subClassas. В рамках методу extension ми створимо внутрішній клас, який називається функцією успадкування наслідування () {}. Причиною того, що ми використовуємо цей внутрішній клас, є уникнення плутанини між прототипами baseClass та subClass. Далі ми робимо прототип нашого класу успадкування вказувати на прототип baseClass, як із наступним кодом: nasledstvo.prototype = baseClass. прототип; Потім ми копіюємо прототип успадкування у прототип підкласу наступним чином: subClass.prototype = new спадкування (); Наступне - вказати конструктор для нашого підкласу наступним чином: subClass.prototype.constructor = subClass; Після завершення прототипування підкласу ми можемо вказати наступні два рядки коду для встановлення деяких покажчиків базового класу.

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

Ось повний код нашої функції розширення:

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

Тепер, коли ми реалізували нашу спадщину, ми можемо почати використовувати її для розширення наших класів. У цьому випадку ми збираємося розширити наш клас Person до класу Manager наступним чином:

Визначаємо клас Manager

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

ми робимо це успадковувати від особи

Inheritance_Manager.extend(Manager, Person);

Якщо ви помітили, ми щойно викликали метод extension нашого класу Inheritance_Manager і передали менеджер підкласів у нашому випадку, а потім baseClass Person. Зауважте, що порядок тут дуже важливий. Якщо ви поміняєте їх місцями, спадщина буде працювати не так, як ви задумали, якщо взагалі. Також зауважте, що вам потрібно буде вказати це успадкування, перш ніж ви зможете фактично визначити наш підклас. Тепер визначимо наш підклас:

Ми можемо додати більше методів, як наведений нижче. Наш клас Manager завжди матиме методи та властивості, визначені в класі Person, оскільки він успадковується від нього.

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

Тепер, щоб протестувати його, давайте створимо два об’єкти, один із класу Person і другий із успадкованого менеджера класів:

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

Не соромтеся отримати повний код та більше коментарів за адресою: http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.