Какое свойство у конструктора позволяет реализовать наследование
Теперь, когда объясняется большая часть подробностей OOJS, эта статья показывает, как создавать «дочерние» классы объектов (конструкторы), которые наследуют признаки из своих «родительских» классов. Кроме того, мы дадим некоторые советы о том, когда и где вы можете использовать OOJS , и посмотрим, как классы рассматриваются в современном синтаксисе ECMAScript.
Необходимые знания: | Базовая компьютерная грамотность, понимание основ HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Структурные элементы) and основы Объектно-ориентированного JS (см. Введение в объекты). |
---|---|
Цель: | Понять, как можно реализовать наследование в JavaScript. |
Прототипное наследование
До сих пор мы видели некоторое наследование в действии – мы видели, как работают прототипы и как элементы наследуются, поднимаясь по цепочке. Но в основном это связано с встроенными функциями браузера. Как создать объект в JavaScript, который наследует от другого объекта?
Давайте рассмотрим, как это сделать на конкретном примере.
Начало работы
Прежде всего сделайте себе локальную копию нашего файла oojs-class-inheritance-start.html (он также работает в режиме реального времени). В файле вы найдете тот же пример конструктора Person(), который мы использовали на протяжении всего модуля, с небольшим отличием – мы определили внутри конструктора только лишь свойства:
function Person(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
};
Все методы определены в прототипе конструктора. Например:
Person.prototype.greeting = function() {
alert(‘Hi! I’m ‘ + this.name.first + ‘.’);
};
Примечание. В исходном коде вы также увидите определенные методы bio() и farewell(). Позже вы увидите, как они могут быть унаследованы другими конструкторами.
Скажем так, мы хотели создать класс Teacher, подобный тому, который мы описали в нашем первоначальном объектно-ориентированном определении, которое наследует всех членов от Person, но также включает в себя:
- Новое свойство, subject – оно будет содержать предмет, который преподает учитель.
- Обновленный метод greeting(), который звучит немного более формально, чем стандартный метод greeting()— более подходит для учителя, обращающегося к некоторым ученикам в школе.
Определение функции-конструктора Teacher()
Первое, что нам нужно сделать, это создать конструктор Teacher() – добавьте ниже следующий код:
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}
Это похоже на конструктор Person во многих отношениях, но здесь есть что-то странное, что мы не видели раньше – функцию call(). Эта функция в основном позволяет вам вызывать функцию, определенную где-то в другом месте, но в текущем контексте. Первый параметр указывает значение this, которое вы хотите использовать при выполнении функции, а остальные параметры – те, которые должны быть переданы функции при ее вызове.
Мы хотим, чтобы конструктор Teacher() принимал те же параметры, что и конструктор Person(), от которго он наследуется, поэтому мы указываем их как параметры в вызове call().
Последняя строка внутри конструктора просто определяет новое свойство subject, которое будут иметь учителя, и которого нет у Person().
В качестве примечания мы могли бы просто сделать это:
function Teacher(first, last, age, gender, interests, subject) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
this.subject = subject;
}
Но это просто переопределяет свойства заново, а не наследует их от Person(), так что теряется смысл того, что мы пытаемся сделать. Он также занимает больше строк кода.
Наследование от конструктора без параметров
Обратите внимание, что если конструктор, от которого вы наследуете, не принимает значения своего свойства из параметров, вам не нужно указывать их в качестве дополнительных аргументов в call(). Так, например, если у вас было что-то действительно простое:
function Brick() {
this.width = 10;
this.height = 20;
}
Вы можете наследовать свойства width и height, выполнив это (как и другие шаги, описанные ниже, конечно):
function BlueGlassBrick() {
Brick.call(this);
this.opacity = 0.5;
this.color = ‘blue’;
}
Обратите внимание, что мы указали только this внутри call() – никаких других параметров не требуется, поскольку мы не наследуем никаких свойств родителя, которые задаются через параметры.
Установка Teacher()’s prototype и конструктор ссылок
Пока все хорошо, но у нас есть проблема. Мы определили новый конструктор и у него есть свойство prototype, которое по умолчанию просто содержит ссылку на саму конструкторскую функцию. Он не содержит методов свойства prototype конструктора Person. Чтобы увидеть это, введите Object.getOwnPropertyNames(Teacher.prototype) в поле ввода текста или в вашу консоль JavaScript. Затем введите его снова, заменив Teacher на Person. Новый конструктор не наследует эти методы. Чтобы увидеть это, сравните выводы в консоль Person.prototype.greeting и Teacher.prototype.greeting. Нам нужно заставить Teacher() наследовать методы, определенные на прототипе Person(). Итак, как мы это делаем?
- Добавьте следующую строку ниже своего предыдущего добавления:
Teacher.prototype = Object.create(Person.prototype);
Здесь наш друг create() снова приходит на помощь. В этом случае мы используем его для создания нового объекта и делаем его значением Teacher.prototype. Новый объект имеет свой прототип Person.prototype и, следовательно, наследует, если и когда это необходимо, все доступные методы Person.prototype. - Нам нужно сделать еще одну вещь, прежде чем двигаться дальше. После добавления последней строки, Teacher.prototype.constructor стало равным Person(), потому что мы просто устанавливаем Teacher.prototype для ссылки на объект, который наследует его свойства от Person.prototype! Попробуйте сохранить код, загрузите страницу в браузере и введите Teacher.prototype.constructor в консоль для проверки.
- Это может стать проблемой, поэтому нам нужно сделать это правильно. Вы можете сделать это, вернувшись к исходному коду и добавив следующие строки внизу:
Object.defineProperty(Teacher.prototype, ‘constructor’, {
value: Teacher,
enumerable: false, // false, чтобы данное свойство не появлялось в цикле for in
writable: true }); - Теперь, если вы сохраните и обновите, введите Teacher.prototype.constructor, чтобы вернуть Teacher(), плюс мы теперь наследуем Person()!
Предоставление Teacher() новой функции greeting()
Чтобы завершить наш код, нам нужно определить новую функцию greeting() в конструкторе Teacher().
Самый простой способ сделать это – определить его на прототипе Teacher() – добавить в нижнюю часть кода следующее:
Teacher.prototype.greeting = function() {
var prefix;
if (this.gender === ‘male’ || this.gender === ‘Male’ || this.gender === ‘m’ || this.gender === ‘M’) {
prefix = ‘Mr.’;
} else if (this.gender === ‘female’ || this.gender === ‘Female’ || this.gender === ‘f’ || this.gender === ‘F’) {
prefix = ‘Mrs.’;
} else {
prefix = ‘Mx.’;
}
alert(‘Hello. My name is ‘ + prefix + ‘ ‘ + this.name.last + ‘, and I teach ‘ + this.subject + ‘.’);
};
Это выводит на экран приветствие учителя, в котором используется соответствующий префикс имени для своего пола, разработанный с использованием условного оператора.
Попробуйте пример
Теперь, когда вы ввели весь код, попробуйте создать экземпляр объекта из Teacher(), поставив ниже внизу вашего JavaScript (или что-то похожее по вашему выбору):
var teacher1 = new Teacher(‘Dave’, ‘Griffiths’, 31, ‘male’, [‘football’, ‘cookery’], ‘mathematics’);
Теперь сохраните, обновите, и попробуйте получить доступ к свойствам и методам вашего нового объекта teacher1, например:
teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();
Все должно работать нормально. Запросы в строках 1, 2, 3 и 6 унаследованны от общего конструктора Person() (класса). Запрос в строке 4 обращается к subject, доступному только для более специализированного конструктора (класса) Teacher(). Запрос в строке 5 получил бы доступ к методу greeting(), унаследованному от Person(), но Teacher() имеет свой собственный метод greeting() с тем же именем, поэтому запрос обращается к этому методу.
Примечание. Если вам не удается заставить это работать, сравните свой код с нашей готовой версией (см. также рабочее демо).
Методика, которую мы здесь рассмотрили, – это не единственный способ создания наследующих классов в JavaScript, но он работает нормально и это дает вам представление о том, как реализовать наследование в JavaScript.
Вам также может быть интересно узнать некоторые из новых функций ECMAScript, которые позволяют нам делать наследование более чисто в JavaScript (см. Classes). Мы не рассматривали их здесь, поскольку они пока не поддерживаются очень широко в браузерах. Все остальные конструкторы кода, которые мы обсуждали в этом наборе статей, поддерживаются еще в IE9 или ранее и есть способы добиться более ранней поддержки, чем это.
Обычный способ – использовать библиотеку JavaScript – большинство популярных опций имеют простой набор функций, доступных для выполнения наследования более легко и быстро. CoffeeScript , например, предоставляет класс, расширяет и т.д.
Дальнейшее упражнение
В нашем руководстве по Объектно-ориентированному JavaScript для начинающих мы также включили класс Student как концепцию, которая наследует все особенности Person, а также имеет другой метод greeting() от Person, который гораздо более неформален, чем приветствие Teacher. Посмотрите, как выглядит приветствие ученика в этом разделе, и попробуйте реализовать собственный конструктор Student(), который наследует все функции Person() и реализует другую функцию greeting().
Примечание. Если вам не удается заставить это работать, сравните свой код с нашей готовой версией (см. также рабочее демо).
Object member summary
Подводя итог, вы в основном получили три типа свойств / методов, о которых нужно беспокоиться:
- Те, которые определены внутри функции-конструктора, которые присваиваются экземплярам объекта. Их довольно легко заметить – в вашем собственном коде они представляют собой элементы, определенные внутри конструктора, используя строки this.x = x; в встроенном коде браузера они являются членами, доступными только для экземпляров объектов (обычно создаются путем вызова конструктора с использованием ключевого слова new, например var myInstance = new myConstructor ().
- Те, которые определяются непосредственно самим конструктором, которые доступны только для конструктора. Они обычно доступны только для встроенных объектов браузера и распознаются путем непосредственной привязки к конструктору, а не к экземпляру. Например, Object.keys().
- Те, которые определены в прототипе конструктора, которые наследуются всеми экземплярами и наследуют классы объектов. К ним относятся любой член, определенный в свойстве прототипа конструктора, например. myConstructor.prototype.x().
Если вы не уверены, что это такое, не беспокойтесь об этом, пока вы еще учитесь и знание придет с практикой.
Когда вы используете наследование в JavaScript?
В частности, после этой последней статьи вы можете подумать: «У-у-у, это сложно». Ну, ты прав. Прототипы и наследование представляют собой некоторые из самых сложных аспектов JavaScript, но многие возможности и гибкость JavaScript вытекают из его структуры объектов и наследования и стоит понять, как это работает.
В некотором смысле вы используете наследование все время. Всякий раз, когда вы используете различные функции веб-API или методы/свойства, определенные во встроенном объекте браузера, который вы вызываете в своих строках, массивах и т.д., вы неявно используете наследование.
Что касается использования наследования в вашем собственном коде, вы, вероятно, не будете часто его использовать, особенно для начала и в небольших проектах. Это пустая трата времени на использование объектов и наследование только ради этого, когда они вам не нужны. Но по мере того, как ваши базы кода становятся больше, вы с большей вероятностью найдете необходимость в этом. Если вы начинаете создавать несколько объектов с подобными функциями, то создание универсального типа объекта, содержащего все общие функции и наследование этих функций в более специализированных типах объектов, может быть удобным и полезным.
Примечание. Из-за того, как работает JavaScript, с цепочкой прототипов и т.д., совместное использование функций между объектами часто называется делегированием. Специализированные объекты делегируют функциональность универсальному типу объекта.
При использовании наследования вам рекомендуется не иметь слишком много уровней наследования и тщательно отслеживать, где вы определяете свои методы и свойства. Можно начать писать код, который временно изменяет прототипы встроенных объектов браузера, но вы не должны этого делать, если у вас нет действительно веской причины. Слишком много наследования могут привести к бесконечной путанице и бесконечной боли при попытке отладки такого кода.
В конечном счете, объекты – это еще одна форма повторного использования кода, например функций или циклов, со своими конкретными ролями и преимуществами. Если вы обнаруживаете, что создаете кучу связанных переменных и функций и хотите отслеживать их все вместе и аккуратно их упаковывать, объект является хорошей идеей. Объекты также очень полезны, когда вы хотите передать коллекцию данных из одного места в другое. Обе эти вещи могут быть достигнуты без использования конструкторов или наследования. Если вам нужен только один экземпляр объекта, вам лучше всего использовать литерал объекта и вам, разумеется, не нужно наследование.
Резюме
В этой статье мы рассмотрели оставшуюся часть основной теории и синтаксиса OOJS, которые, как мы думаем, вам следует знать сейчас. На этом этапе вы должны понимать основы JavaScript, ООП, прототипы и прототипное наследование, как создавать классы (конструкторы) и экземпляры объектов, добавлять функции в классы и создавать подклассы, которые наследуются от других классов.
В следующей статье мы рассмотрим, как работать с JavaScript Object Notation (JSON), общим форматом обмена данными, написанным с использованием объектов JavaScript.
See also
- ObjectPlayground.com — A really useful interactive learning site for learning about objects.
- Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
- You Don’t Know JS: this & Object Prototypes — Part of Kyle Simpson’s excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We’ve presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.
В этом модуле
- Основы объекта
- Объектно-ориентированный JavaScript для начинающих
- Прототипы объектов
- Наследование в JavaScript
- Работа с данными JSON
- Практика построения объектов
- Добавление функций в нашу демонстрацию прыгающих шаров
Источник
Модель наследования в JavaScript может озадачить опытных разработчиков на высокоуровневых объектно-ориентированных языках (таких, например, как Java или C++), поскольку она динамическая и не включает в себя реализацию понятия class (хотя ключевое слово class, бывшее долгие годы зарезервированным, и приобрело практическое значение в стандарте ES2015, однако, Class в JavaScript ES>=6 представляет собой лишь “синтаксический сахар” поверх прототипно-ориентированной модели наследования).
В плане наследования JavaScript работает лишь с одной сущностью: объектами. Каждый объект имеет внутреннюю ссылку на другой объект, называемый его прототипом. У объекта-прототипа также есть свой собственный прототип и так далее до тех пор, пока цепочка не завершится объектом, у которого свойство prototype равно null. По определению, null не имеет прототипа и является завершающим звеном в цепочке прототипов.
Хотя прототипную модель наследования некоторые относят к недостаткам JavaScript, на самом деле она мощнее классической. К примеру, поверх неё можно предельно просто реализовать классическое наследование, а вот попытки совершить обратное непременно вынудят вас попотеть.
Наследование с цепочкой прототипов
Наследование свойств
Объекты в JavaScript — динамические “контейнеры”, наполненные свойствами (называемыми собственными свойствами). Каждый объект содержит ссылку на свой объект-прототип.
При попытке получить доступ к какому-либо свойству объекта, свойство вначале ищется в самом объекте, затем в прототипе объекта, после чего в прототипе прототипа, и так далее. Поиск ведется до тех пор, пока не найдено свойство с совпадающим именем или не достигнут конец цепочки прототипов.
// В этом примере someObject.[[Prototype]] означает прототип someObject.
// Это упрощённая нотация (описанная в стандарте ECMAScript).
// Она не может быть использована в реальных скриптах.
// Допустим, у нас есть объект ‘o’ с собственными свойствами a и b
// {a:1, b:2}
// o.[[Prototype]] имеет свойства b и с
// {b:3, c:4}
// Далее, o.[[Prototype]].[[Prototype]] является null
// null – это окончание в цепочке прототипов
// по определению, null не имеет свойства [[Prototype]]
// В итоге полная цепочка прототипов выглядит так:
// {a:1, b:2} —> {b:3, c:4} —> null
console.log(o.a); // 1
// Есть ли у объекта ‘o’ собственное свойство ‘a’?
// Да, и его значение равно 1
console.log(o.b); // 2
// Есть ли у объекта ‘o’ собственное свойство ‘b’?
// Да, и его значение равно 2.
// У прототипа o.[[Prototype]] также есть свойство ‘b’,
// но обращения к нему в данном случае не происходит.
// Это и называется “property shadowing” (затенение свойства)
console.log(o.c); // 4
// Есть ли у объекта ‘o’ собственное свойство ‘с’?
// Нет, тогда поищем его в прототипе.
// Есть ли у объекта o.[[Prototype]] собственное свойство ‘с’?
// Да, оно равно 4
console.log(o.d); // undefined
// Есть ли у объекта ‘o’ собственное свойство ‘d’?
// Нет, тогда поищем его в прототипе.
// Есть ли у объекта o.[[Prototype]] собственное свойство ‘d’?
// Нет, продолжаем поиск по цепочке прототипов.
// o.[[Prototype]].[[Prototype]] равно null, прекращаем поиск,
// свойство не найдено, возвращаем undefined
При добавлении к объекту нового свойства, создаётся новое собственное свойство (own property). Единственным исключением из этого правила являются наследуемые свойства, имеющие getter или setter.
Наследование “методов”
JavaScript не имеет “методов” в смысле, принятом в классической модели ООП. В JavaScript любая функция может быть добавлена к объекту в виде его свойства. Унаследованная функция ведёт себя точно так же, как любое другое свойство объекта, в том числе и в плане “затенения свойств” (property shadowing), как показано в примере выше (в данном конкретном случае это форма переопределения метода – method overriding).
В области видимости унаследованной функции ссылка this указывает на наследующий объект (на наследника), а не на прототип, в котором данная функция является собственным свойством.
var o = {
a: 2,
m: function(){
return this.a + 1;
}
};
console.log(o.m()); // 3
// в этом случае при вызове ‘o.m’ this указывает на ‘o’
var p = Object.create(o);
// ‘p’ – наследник ‘o’
p.a = 12; // создаст собственное свойство ‘a’ объекта ‘p’
console.log(p.m()); // 13
// при вызове ‘p.m’ this указывает на ‘p’.
// т.е. когда ‘p’ наследует функцию ‘m’ объекта ‘o’,
// this.a означает ‘p.a’, собственное свойство ‘a’ объекта ‘p’
Различные способы создания объектов и получаемые в итоге цепочки прототипов
Создание объектов с помощью литералов
var o = {a: 1};
// Созданный объект ‘o’ имеет Object.prototype в качестве своего [[Prototype]]
// у ‘o’ нет собственного свойства ‘hasOwnProperty’
// hasOwnProperty — это собственное свойство Object.prototype.
// Таким образом ‘o’ наследует hasOwnProperty от Object.prototype
// Object.prototype в качестве прототипа имеет null.
// o —> Object.prototype —> null
var a = [“yo”, “whadup”, “?”];
// Массивы наследуются от Array.prototype
// (у которого есть такие методы, как indexOf, forEach и т.п.).
// Цепочка прототипов при этом выглядит так:
// a —> Array.prototype —> Object.prototype —> null
function f(){
return 2;
}
// Функции наследуются от Function.prototype
// (у которого есть такие методы, как call, bind и т.п.):
// f —> Function.prototype —> Object.prototype —> null
Создание объектов с помощью конструктора
В JavaScript “конструктор” — это “просто” функция, вызываемая с оператором new.
function Graph() {
this.vertexes = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertexes.push(v);
}
}
var g = new Graph();
// объект ‘g’ имеет собственные свойства ‘vertexes’ и ‘edges’.
// g.[[Prototype]] принимает значение Graph.prototype при выполнении new Graph().
Object.create
В ECMAScript 5 представлен новый метод создания объектов: Object.create. Прототип создаваемого объекта указывается в первом аргументе этого метода:
var a = {a: 1};
// a —> Object.prototype —> null
var b = Object.create(a);
// b —> a —> Object.prototype —> null
console.log(b.a); // 1 (унаследовано)
var c = Object.create(b);
// c —> b —> a —> Object.prototype —> null
var d = Object.create(null);
// d —> null
console.log(d.hasOwnProperty);
// undefined, т.к. ‘d’ не наследуется от Object.prototype
Используя ключевое слово class
С выходом ECMAScript 6 появился целый набор ключевых слов, реализующих классы. Они могут показаться знакомыми людям, изучавшим языки, основанные на классах, но есть существенные отличия. JavaScript был и остаётся прототипно-ориентированным языком. Новые ключевые слова: “class”, “constructor”, “static”, “extends” и “super”.
“use strict”;
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
Производительность
Длительное время поиска свойств, располагающихся относительно высоко в цепочке прототипов, может негативно сказаться на производительности (performance), особенно в критических в этом смысле местах кода. Кроме того, попытка найти несуществующие свойства неизбежно приведёт к проверке на их наличие у всех объектов цепочки прототипов.
Кроме того, при циклическом переборе свойств объекта будет обработано каждое свойство, присутствующее в цепочке прототипов.
Если вам необходимо проверить, определено ли свойство у самого объекта, а не где-то в его цепочке прототипов, вы можете использовать метод hasOwnProperty, который все объекты наследуют от Object.prototype.
hasOwnProperty — единственная существующая в JavaScript возможность работать со свойствами, не затрагивая цепочку прототипов.
Примечание: Для проверки существования свойства недостаточно проверять, эквивалентно ли оно undefined. Свойство может вполне себе существовать, но при этом ему может быть присвоено значение undefined.
Плохая практика: расширение базовых прототипов
Одной из частых ошибок является расширение Object.prototype или других базовых прототипов.
Такой подход называется monkey patching и нарушает принцип инкапсуляции. Несмотря на то, что ранее он использовался в таких широко распространенных фреймворках, как например, Prototype.js, в настоящее время не существует разумных причин для его использования, поскольку в данном случае встроенные типы “захламляются” дополнительной нестандартной функциональностью.
Единственным оправданием расширения базовых прототипов могут являться лишь полифиллы – эмуляторы новой функциональности (например, Array.forEach) для не поддерживающих её реализаций языка в старых веб-браузерах.
Примеры
B наследует от A:
function A(a){
this.varA = a;
}
// What is the purpose of including varA in the prototype when A.prototype.varA will always be shadowed by
// this.varA, given the definition of function A above?
A.prototype = {
varA : null, // Shouldn’t we strike varA from the prototype as doing nothing?
// perhaps intended as an optimization to allocate space in hidden classes?
// https://developers.google.com/speed/articles/optimizing-javascript#Initializing instance variables
// would be valid if varA wasn’t being initialized uniquely for each instance
doSomething : function(){
// …
}
}
function B(a, b){
A.call(this, a);
this.varB = b;
}
B.prototype = Object.create(A.prototype, {
varB : {
value: null,
enumerable: true,
configurable: true,
writable: true
},
doSomething : {
value: function(){ // переопределение
A.prototype.doSomething.apply(this, arguments); // call super
// …
},
enumerable: true,
configurable: true,
writable: true
}
});
B.prototype.constructor = B;
var b = new B();
b.doSomething();
Важно:
- Типы определяются в .prototype
- Для наследования используется Object.create()
prototype и Object.getPrototypeOf
Как уже упоминалось, JavaScript может запутать разработчиков на Java или C++, ведь в нём совершенно нет “нормальных” классов. Всё, что мы имеем – лишь объекты. Даже те “classes”, которые мы имитировали в статье, тоже являются функциональными объектами.
Вы наверняка заметили, что у function A есть особое свойство prototype. Это свойство работает с оператором new. Ссылка на объект-прототип копируется во внутреннее свойство [[Prototype]] нового объекта. Например, в этом случае var a1 = new A(), JavaScript (после создания объекта в памяти и до выполнения функции function A() ) устанавливает a1.[[Prototype]] = A.prototype. Потом, при попытке доступа к свойству нового экземпляра объекта, JavaScript проверяет, принадлежит ли свойство непосредственно объекту. Если нет, то интерпретатор ищет в свойстве [[Prototype]]. Всё, что было определено в prototype, в равной степени доступно и всем экземплярам данного объекта. При внесении изменений в prototype все эти изменения сразу же становятся доступными и всем экземплярам объекта.
[[Prototype]] работает рекурсивно, то есть при вызове:
var o = new Foo();
JavaScript на самом деле выполняет что-то подобное:
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);
а когда вы делаете так:
o.someProp;
JavaScript проверяет, есть ли у o свойство someProp.
и если нет, то проверяет Object.getPrototypeOf(o).someProp
а если и там нет, то ищет в Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp и так далее.
Заключение
Важно чётко понимать принципы работы прототипной модели наследования, прежде чем начинать писать сложный код с её использованием.
При написании JavaScript-кода, использующего наследование, следует помнить о длине цепочек прототипов и стараться делать их как можно более короткими во избежание проблем с производительностью во время выполнения кода.
Расширять базовые прототипы следует исключительно для поддержания совместимости кода с отдельными “древними” реализациями JavaScript, – во всех прочих случаях это плохая практика.
Источник