Хороший приклад успадкування на основі прототипу JavaScript


89

Я програмую на мовах ООП більше 10 років, але зараз я вивчаю JavaScript, і вперше я зіткнувся з успадкуванням на основі прототипів. Я прагну швидше вчитися, вивчаючи хороший код. Що є добре написаним прикладом програми JavaScript (або бібліотеки), яка належним чином використовує прототипне успадкування? І чи можете ви коротко описати, як / де використовується прототипне успадкування, щоб я знав, з чого почати читати?


1
Чи ви мали можливість перевірити цю базову бібліотеку? Це справді приємно, і зовсім маленьке. Якщо вам це подобається, розгляньте можливість позначити мою відповідь як відповідь. TIA, Роланд.
Роланд Буман,

Я думаю, я перебуваю в одному човні з вами. Я також хочу трохи дізнатися про цю прототипну мову, не обмежуючись лише рамками oop або подібними, навіть вони чудові, і все, нам потрібно вчитися, так? Це робить не просто якийсь фреймворк, навіть якщо я збираюся його використовувати. Але навчіться створювати нові речі новими мовами новими способами, думайте нестандартно. Мені подобається твій стиль. Я намагаюся допомогти мені і, можливо, допоможу вам. Як тільки я щось знайду, я дам вам знати.
marcelo-ferraz

Відповіді:


48

Дуглас Крокфорд має гарну сторінку про прототипне успадкування JavaScript :

П'ять років тому я написав " Класичне успадкування" на JavaScript. Це показало, що JavaScript є вільною від класу прототиповою мовою і що він має достатньо виражальної сили для моделювання класичної системи. З тих пір мій стиль програмування еволюціонував, як і слід було робити будь-якому хорошому програмісту. Я навчився повністю сприймати прототипізм і звільнився від обмежень класичної моделі.

Дін Едвард Base.js , клас Mootools або Просте успадкування Джона Резіга - це способи класичного успадкування в JavaScript.


Чому б не просто, newObj = Object.create(oldObj);якщо ви хочете, щоб це було без занять? В іншому випадку, замінити oldObjна об'єкт-прототип функції конструктора повинна працювати?
Байкер

76

Як уже згадувалося, фільми Дугласа Крокфорда дають гарне пояснення того, чому і охоплюють те, як. Але якщо помістити це в кілька рядків JavaScript:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

Проблема цього підходу полягає в тому, що він буде створювати об'єкт щоразу, коли ви його створюєте. Інший підхід полягає у оголошенні ваших об’єктів у стеці прототипів, наприклад:

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

Що стосується самоаналізу, є невеликий мінус. Тест на скидання One, призведе до менш корисної інформації. Також приватна власність "privateVariable" у "testOne" спільно використовується у всіх випадках, також корисно згадано у відповідях shesek.


3
Зверніть увагу, що в testOne privateVariable- це просто змінна в обсязі IIFE , і вона спільно використовується між усіма екземплярами, тому вам не слід зберігати на ній дані, присвячені екземпляру. (на testTwo це специфічно для екземпляра, оскільки кожен виклик testTwo () створює новий, для кожного екземпляра, обсяг)
shesek

Я проголосував за те, що ви показали інший підхід, і чому б його не використовувати, тому що він робить копії
Murphy316

Проблема відтворення об'єкта кожного разу в основному пов'язана з методами, що відтворюються для кожного нового об'єкта. Однак ми можемо пом'якшити проблему, визначивши метод Dog.prototype. Тому замість використання this.bark = function () {...}ми можемо робити Dot.prototype.bark = function () {...}поза Dogфункцією. (Докладніше див. У цій відповіді )
Хуанг Чао,

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
Можливо, додавання цього посилання до вашої відповіді може ще більше доповнити картину: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Dynom

14

Я б подивився YUI та Baseбібліотеку Діна Едварда : http://dean.edwards.name/weblog/2006/03/base/

Для YUI ви можете швидко поглянути на модуль lang , особливо. YAHOO.lang.extend метод. А потім ви можете переглянути джерело деяких віджетів або утиліт і побачити, як вони використовують цей метод.


Станом на 2011 рік YUI 2 застаріло, тому посилання на langнього напіврозірвано. Хтось потребує виправлення для YUI 3?
ack

lang в yui 3, схоже, не має методу розширення. але оскільки відповідь має намір використати реалізацію як приклад, версія не має значення.
eMBee


4

Це найяскравіший приклад, який я знайшов з книги Node Mixu ( http://book.mixu.net/node/ch6.html ):

Я віддаю перевагу композиції над спадщиною:

Композиція - Функціональність об’єкта складається з сукупності різних класів, що містить екземпляри інших об’єктів. Спадкування - функціональність об’єкта складається з його власної функціональності плюс функціональності батьківських класів. Якщо вам потрібно отримати спадщину, використовуйте звичайний старий JS

Якщо вам потрібно реалізувати успадкування, принаймні уникайте використання ще однієї нестандартної функції реалізації / магії. Ось як ви можете реалізувати розумний факсиміле успадкування в чистому ES3 (за умови дотримання правила ніколи не визначати властивості на прототипах):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

Це не те саме, що класичне успадкування - але це стандартний, зрозумілий Javascript і має функціонал, якого люди переважно шукають: конструктивні схеми та можливість викликати методи суперкласу.


4

ES6 classтаextends

ES6 classі extendsє лише синтаксичним цукром для раніше можливих маніпуляцій прототипом ланцюга, і тому, мабуть, найбільш канонічна установка.

Спочатку дізнайтеся більше про ланцюжок прототипів та .пошук властивостей за адресою: https://stackoverflow.com/a/23877420/895245

А тепер давайте деконструюємо те, що відбувається:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Спрощена схема без усіх заздалегідь визначених об'єктів:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

Найкращі приклади, які я бачив, - у JavaScript Дугласа Крокфорда : Гарні частини . Однозначно варто купувати, щоб допомогти отримати збалансований погляд на мову.

Дуглас Крокфорд відповідає за формат JSON і працює в Yahoo як гуру JavaScript.


7
відповідальний? це звучить майже як "винуватий" :)
Роланд Буман

@Roland Я думаю, що JSON - це досить приємний не детальний формат для зберігання даних. Однак він точно не придумав цього, формат був там для налаштувань конфігурації в Steam ще в 2002 році
Chris S

Кріс S, я теж так думаю - все частіше і частіше я бажаю, щоб ми всі могли пропустити XML як формат обміну і відразу ж перейти на JSON.
Роланд Буман

3
Не так багато для вигадування: JSON - це підмножина синтаксису власного об’єктного літералу, який існує в мові приблизно з 1997 року.
Тім Даун,

@ Час хороший момент - я не розумів, що він був там із самого початку
Chris S


0

Додавання прикладу успадкування на основі прототипу в Javascript.

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

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

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6 використовує набагато простішу реалізацію успадкування за допомогою конструктора та супер ключових слів.

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