asd asdsa f
Статьи

Javascript prototype — как на самом деле работает прототипное наследование в JavaScript

0 1190

Что такое javascript prototype? Вы знали что свойство prototype не является на самом деле прототипом объекта в javascript? Если да, то эта статья для вас.

Идея прототипного наследования на поверхности довольно проста. В «классическом» ООП объекты наследуют свойства и методы класса объектов. При прототипном наследовании же происходит наследование полей и методов напрямую от некоторого другого объекта — прототипа. О том, как именно это работает в javascript читайте в этой статье.

Казалось бы все просто, но javascript привносит тут несколько сумбурных вещей. Я, например, долго не мог до конца въехать, как же работает прототипное наследование в javascript. На деле оказывается все не так уж и сложно.

Далее я привожу свой вольный перевод этой статьи, которая весьма недурно объясняет основные особенности работы прототипного наследования в javascript. 

Прототипы в Javascript создают сумятицу везде, где бы они ни использовались. Даже опытные разработчики порой не до конца понимают как это все работает. Мне кажется, в основном это связано с некоторой мешаниной в конструкциях языка — оператор new, constructor объекта и св-во prototype любого объекта-функции. На самом же деле по природе своей прототип — это очень простое понятие. Чтобы разобраться во всем подробно и аккуратно, давайте забудем все, что мы знали о прототипном наследовании в javascript, и начнем с самого начала.

Что такое prototype (прототип)?

Прототип — это объект, от которого наследуют св-ва другие объекты.

Любой объект может быть прототипом?

Да

У каких объектов есть прототипы?

По умолчанию, у любого объекта есть прототип. Прототип является объектом, поэтому он сам имеет прототип.

Постой! Еще раз — что такое объект?

Объект в JavaScript это просто неупорядоченный набор пар ключ-значение. Кроме примитивных типов (number, string, boolean) и специальных значений uindefined и null, все является объектом.

Ты сказал, что у каждого объекта есть прототип. Но если я напишу ({}).prototype, то я получу undefined. В чем тут дело?

Забудь все, что ты, возможно, знаешь о свойстве prototype. Это один из самых больших источников недоразумений в понимании работы прототипного наследования в javascript. Настоящий прототип объекта содержится во внутреннем свойстве [[Prototype]]. Стандарт ECMA 5 вводит стандартный метод получения прототипа с помощью ф-ии Object.getPrototypeOf(object). Также, практически все браузеры поддерживают не утвержденное стандартом св-во __proto__. Пример:

var a = {};

//Не работает в IE<=8
Object.getPrototypeOf(a); //[object Object]

//Не работает IE
a.__proto__; //[object Object]

//Для всех браузеров
//Также позволяет получить прототип объекта, но при условии, что св-во не было переопределено (см. ниже)
a.constructor.prototype; //[object Object]

Так, ладно, но false примитивного типа, почему тогда false.__proto__ возвращает значение.

Очередная магия JavaScript. Когда у примитива запрашивают прототип, он неявно преобразуется в соответствующий объект.

false.__proto__ === Boolean(false).__proto__

Итак, я хочу использовать прототип для наследования. Что нужно сделать?

Давайте сразу договоримся, что мы не хотим использовать прототипы для наследования свойств одного объекта от другого. В конце концов, это можно сделать простым добавлением свойств к конечному объекту. Тем не менее, если мы, например, хотим унаследовать все свойства у некого часто-используемого объекта, то мы можем сделать что-то вроде:

var a = {};
a.__proto__ = Array.prototype;
a.length; //получили доступ ко всем свойствам (и методам) стандартного объекта-массива

Настоящая польза от использования прототипов появляется тогда, когда множество объектов наследуют один и тот-же прототип. Тогда не нужно заботиться о копировании свойств при каждом создании объекта.

Вот тут как раз и используется конструктор?

Да. Конструктор предоставляет механизм присваивания общего прототипа создаваемому объекту.

Прежде чем ты приведешь привер мне нужно понять, чтоже означает свойство constructor.prototype?

Ок. В JavaScript потенциально любая определенная пользователем функция может быть конструктором. По умолчанию, функция сразу получает свойство prototype. И Все, что не является функцией, такого св-ва не имеет.

//объект-функция не станет конструктором, но в данном случае у нее все равно есть прототип
(new Function()).prototype; //[object Object]

//это самый стандартный случай - новая функция автоматически получает св-во proptotype
var A = function(name) {
    this.name = name;
}
A.prototype; //[object Object]

//Math это не ф-я поэтому у него нет св-ва prototype (сам прототип, конечно же есть, доступный через __proto__)
Math.prototype; //null

Поэтому определение таково: свойство функции prototype  это объект, который будет назначен в качестве прототипа всем объектам, созданным при использовании этой функции в качестве конструктора.

То есть, свойство prototype объекта-функции не является ее прототипом:


var A = function(name) {
    this.name = name;
}

A.prototype == A.__proto__; //false
A.__proto__ == Function.prototype; //true

 Пример в студию!

Вот пресловутый пример использования прототипов:


//Конструктор. <em>this</em> является ссылкой на новый объект и его внутреннее св-во [[prototype]] будет ссылаться на св-во prototype данного кнструктора
var Circle = function(radius) {
    this.radius = radius;
    //потом автоматически произойдет примерно следующее (только для демонстрации, нет необходимости этого делать явно)
    //this.__proto__ = Circle.prototype;
}

//теперь можно немного расширить прототип функции, добавив туда нужные методы и свойства.
Circle.prototype.area = function() {
   return Math.PI*this.radius*this.radius;
}

//Создаем объекты при вызове функций как конструкторов с помощью оператора new. Эти объекты наследуют общий прототип, поэтому им автоматически доступен метод area.
var a = new Circle(3), b = new Circle(4);
a.area().toFixed(2); //28.27
b.area().toFixed(2); //50.27

Круто! Значит если я изменю прототип конструктора, то все созданные этим конструктором объекты стразу получат эти изменения?

Верно, но нужно уточнить… Если я изменю сам объект-прототип, на который ссылается свойство prototype функции, то ответ — да. Это верно потому что __proto__ объекта ссылается на тот объект, на который ссылался prototype ф-ии во время создания этого объекта. Однако, если заменить само свойство prototype, присвоив ему другой объект, то, конечно, уже созданные объекты этих изменения не увидят. Им уже не важно, куда ссылается св-во prototype.

Сравните:

var A = function(name) {
    this.name = name;
}

var a = new A('alpha');
a.name; //'alpha'

A.prototype.x = 23;

a.x; //23

и


var A = function(name) {
    this.name = name;
}

var a = new A('alpha');
a.name; //'alpha'

A.prototype = {x:23};

a.x; //null

Как выглядит прототип по умолчанию?

Это объект с единственным св-м constructor, которое указывает на данную функцию:


var A = function() {};
A.prototype.constructor == A; //true

var a = new A();
a.constructor == A; //true

 Как работает оператор instanceof в прототипном наследовании?

Выражение a instanceof A означает, что св-во prototype (объект, на который оно ссылается), находится в цепочке прототипов объекта a. То есть, мы можем легко одурачить instanceof (или себя?):

var A = function() {}

var a = new A();
a.__proto__ == A.prototype; //true
a instanceof A; //true;

//Нарушаем цепочку прототипов, просто переопределяя прототип объекта
a.__proto__ = Function.prototype;

a instanceof A; //false

 Что это вообще такое — цепочка прототипов?

Каждый объект имеет прототип, а этот прототип, являясь также объектом, тоже имеет прототип. Эта последовательность прототипов и называется цепочкой прототипов. В коне каждой цепочки всегда находится прототип стандартного объекта Object. Вот у прототипа стандартного объекта прототипа уже не будет. Это единственное исключение из общего правила:

a.__proto__ = b;
b.__proto__ = c;
c.__proto__ = {}; //объект по умолчанию
{}.__proto__.__proto__; //null

Прототипное наследование работает неявно. Когда у некого объекта запраiивается св-во foo, оно сначала проверяется непосредственно у этого объекта. Затем, если св-во не найдено, оно ищется в прототипе объекта, и так далее пока св-во либо не будет найдено, либо вернется undefined.

Установка свойства объекта никак не влияет на прототип. Значение записывается непосредственно в данный объект. Чтобы изменить именно прототип, необходимо к нему обратиться явно.

Если вам понравилась статья, то пожалуйста, жмите поделиться, ставьте плюсы и оставляйте ваши комментарии.

 

 

 

About the author / 

admin