
Когда я впервые перешел с Ruby на JavaScript, мне было сложно понять концепцию прототипа объекта и цепочку прототипов. Ключевое слово class, введенное в ES2015, является просто синтаксическим сахаром и не облегчает понимание.
Тем не менее, независимо от того, на основе прототипа или класса, причина иметь порядок разрешения цепочки или метода (в Python) состоит в том, чтобы установить связи между классами и лучше использовать отношения наследования.
Что касается наследования, в JavaScript есть только одна конструкция: объекты. У каждого объекта есть частное свойство, называемое prototype object, которое действует как объект шаблона, который наследует методы и свойства от другого объекта (например, __mro__ в Python). У этого объекта-прототипа есть собственный прототип, и так до тех пор, пока не будет достигнут объект с null в качестве прототипа. По определению, null не имеет прототипа и действует как последнее звено в этой цепочке прототипов. Перед достижением null предпоследняя остановка должна быть Object, поскольку почти все объекты в JavaScript являются экземплярами Object.
Как это работает? Что ж, давайте посмотрим на пример.
let f = function () { this.a = 1; this.b = 2; } let o = new f(); // {a: 1, b: 2}f.prototype.b = 3; f.prototype.c = 4;
Сейчас:
- o.[[Prototype]] имеет свойства b и c, и
- o.[[Prototype]].[[Prototype]]is Object.prototype, и
- o.[[Prototype]].[[Prototype]].[[Prototype]] имеет значение null.
Итак, вся цепочка собственности выглядит так:
{a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
Обратите внимание, что {a: 1, b: 2} являются собственными свойствами объекта f, которые не наследуются ни от кого другого.
Когда мы пытаемся получить доступ к o.b, мы получаем значение 2. Почему не 3? Потому что .b существует как f собственное свойство, и если оно определено, то значение считывается, и обход цепочки прототипов здесь останавливается. Даже у прототипа есть свойство b, но оно не посещается. Это называется Затенение свойств.
Давайте рассмотрим более конкретный пример прохождения по цепочке прототипов.
function doSomething(){} doSomething.prototype.foo = "bar"; var doSomeInstancing = new doSomething(); doSomeInstancing.prop = "some value";console.log(doSomeInstancing.prop); //some value console.log(doSomeInstancing.foo); // bar console.log(doSomething.prop); // undefined console.log(doSomething.foo); // undefined console.log(doSomething.prototype.prop);// undefined console.log(doSomething.prototype.foo); // bar
Как видно выше, __proto__ из doSomeInstancing равно doSomething.prototype.
Когда вы получаете доступ к свойству doSomeInstancing, браузер сначала проверяет, есть ли у doSomeInstancing это свойство.
Если doSomeInstancing не имеет свойства, браузер ищет свойство в __proto__ из doSomeInstancing (он же doSomething.prototype).
Если __proto__ doSomeInstancing не имеет свойства, тогда браузер ищет __proto__ из __proto__ doSomeInstancing, в данном случае window.Object.prototype.
Если свойство не найдено, просматривается __proto__ из __proto__ из __proto__ doSomeInstancing i, и в этом случае null.
Но __proto__ не существует на нуле. Таким образом, возвращается результат undefined.

Короче говоря, prototype - это свойство объекта Function. Это прототип объектов, созданных этой функцией.
__proto__ - внутреннее свойство объекта, указывающее на его прототип.
function example(x, y) {
this.x = x;
this.y = y;
}
Когда JavaScript выполняет этот код, он добавляет свойство prototype к example, свойство prototype - это объект с двумя свойствами:
constructor
__proto__
Итак, когда мы делаем example.prototype, он возвращается
constructor: example(x,y)
__proto__: Object
Теперь, как вы можете видеть, constructor - это не что иное, как сама функция example, а __proto__ указывает на корневой уровень Object JavaScript.
var myExample = new example();
В строке выше мы создаем экземпляр example. Как это работает?
- Сначала он создает новый пустой объект
{} - Он создает
__proto__наmyExampleи указывает наexample.prototype, поэтомуmyExample.__proto__ === example.prototype - Он выполняет
example.prototype.constructor(что является определением функцииexample) с вновь созданным пустым объектом в качестве контекста (this), поэтому свойствоx,yдобавляется к вновь созданному объекту. - Возвращает вновь созданный объект
// the following are all true
myExample.__proto__ == example.prototype
myExample.__proto__.__proto__ == Object.prototype
myExample.__proto__.__proto__.__proto__ == null
myExample instanceof example;
myExample instanceof Object;
Существуют разные способы создания объектов и результирующей цепочки прототипов.
- Объекты, созданные с помощью синтаксических конструкций
var o = {a: 1};
- С конструктором
«Конструктор» в JavaScript - это «просто» функция, которая вызывается с помощью оператора new.
function Rectangle() { this.length = 2; this.width = 3; }Rectangle.prototype = { area: function() { this.length * this.width; } };var re = new Rectangle(); // re is an object with own properties 'length' and 'width'. // re.[[Prototype]] is the value of Rectangle.prototype when new Rectangle() is executed.
- С
Object.create
В ECMAScript 5 появился новый метод: Object.create(). Вызов этого метода создает новый объект. прототип этого объекта является первым аргументом функции:
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)
- С ключевым словом
class
ECMAScript 2015 представил новый набор ключевых слов, реализующих классы. Новые ключевые слова включают class, constructor, static, extends и super.
Будьте осторожны, открывая или изменяя prototype.
Обратите внимание, что __proto__ считается амортизированным. Если возможно, вам следует использовать следующие методы.
Object.create(proto, [descriptors])- создает пустой объект с заданнымprotoas[[Prototype]]и дополнительными дескрипторами свойств.Object.getPrototypeOf(obj)- возвращает[[Prototype]]изobj.Object.setPrototypeOf(obj, proto)- устанавливает[[Prototype]]дляobjнаproto.
Но не изменяйте prototype объекта на лету, если это возможно, так как он должен создаваться только при создании объекта. Механизмы JavaScript оптимизированы для этого. Изменение prototype на более позднем этапе нарушит внутреннюю оптимизацию операций доступа к свойствам объекта.
Вот и все сегодня!
Приятного чтения!