Хороший пример наследования на основе прототипа JavaScript

Я программировал языки OOP более 10 лет, но сейчас изучаю JavaScript, и это первый раз, когда я сталкивался с наследованием на основе прототипов. Я стараюсь учиться быстрее, изучая хороший код. Какой хорошо написанный пример приложения (или библиотеки) JavaScript, который правильно использует прототипное наследование? И можете ли вы описать (кратко), как/где используется прототипное наследование, поэтому я знаю, с чего начать читать?

Ответ 1

У Дугласа Крокфорда хорошая страница на Наследование прототипа JavaScript:

Пять лет назад я написал Classical Inheritance в JavaScript. Он показал, что JavaScript - это бесклассовый, прототипный язык, и что он обладает достаточной выразительной способностью имитировать классическую систему. С тех пор мой стиль программирования развивается, как и любой хороший программист. Я научился полностью охватить прототипализм и освободился от границ классической модели.

Дин Эдвард Base.js, Mootools Class или John Resig Simple Inheritance - это способы сделать классическое наследование в JavaScript.

Ответ 2

Как уже упоминалось, фильмы Дугласа Крокфорда дают хорошее объяснение тому, почему и как он охватывает то, как. Но поставить его в пару строк 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');

Есть небольшой недостаток, когда дело касается интроспекции. Демпинг testOne приведет к менее полезной информации. Также частная собственность "privateVariable" в "testOne" делится во всех случаях, als, которая упоминается в ответах shesek.

Ответ 3

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);

Ответ 4

Я хотел бы взглянуть на YUI, а у Дина Эдварда Base библиотеки: http://dean.edwards.name/weblog/2006/03/base/

Для YUI вы можете быстро взглянуть на модуль lang, esp. метод YAHOO.lang.extend. Затем вы можете просмотреть источник некоторых виджетов или утилит и посмотреть, как они используют этот метод.

Ответ 6

ES6 class и extends

ES6 class и extends являются просто синтаксическим сахаром для ранее возможной манипуляции с прототипом и, возможно, самой канонической установкой.

Узнайте больше о цепочке прототипов и поиске свойств . по адресу: fooobar.com/questions/539/...

Теперь давайте разберемся, что произойдет:

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:
// https://stackoverflow.com/questions/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

Ответ 7

Это самый яркий пример, который я нашел из книги Mixu Node (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 и имеет функциональность, которая люди в основном ищут: цепные конструкторы и способность звонить методы суперкласса.

Ответ 9

Лучшие примеры, которые я видел, в Douglas Crockford JavaScript: Хорошие части. Это определенно стоит покупать, чтобы помочь вам получить сбалансированный взгляд на язык.

Дуглас Крокфорд отвечает за формат JSON и работает в Yahoo как гуру JavaScript.

Ответ 10

Существует фрагмент Наследование на основе прототипа JavaScript с конкретными реализациями версии ECMAScript. Он автоматически выберет, что использовать между ES6, ES5 и ES3 в соответствии с текущей средой выполнения.

Ответ 11

Добавление примера наследования на основе прототипов в 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," 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 использует гораздо более простую реализацию наследования с использованием конструктора и супер-ключевых слов.