Источник: http://dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/
ECMAScript – это объектно-ориентированный язык программирования с прототипной организацией.Рассмотрим общую теорию и ключевые моменты этих парадигм.
В классовой организации присутствует понятие класса (class) и сущности (instance), принадлежащей данной классификации. Сущности класса также часто называют объектами (object) или экземплярами.
Класс представляет собой формальное абстрактное множество обобщённых характеристик сущности (знаний об объектах).
Понятие множество в этом отношении более близко к математике, однако, можно говорить о типе или классификации.
Пример (здесь и ниже – примеры будут псевдокодом):
C = Class {a, b, c} // класс C, с характеристиками a, b, c
К характеристикам сущностей относятся свойства (описание объекта) и методы (активность объекта).
Сами характеристики, также могут быть представлены объектами.
При этом, объекты хранят состояние (т.е. конкретные значения всех свойств, описанных в классе), а классы определяют жёсткую структуру (т.е. наличие тех или иных свойств) и жёсткое поведение (наличие тех или иных методов) своих экземпляров.
C = Class {a, b, c, method1, method2}
c1 = {a: 10, b: 20, c: 30} // объект с1 класса С
c2 = {a: 50, b: 60, c: 70} // объект с2 со своим состоянием, того же класса С
Итак, имеем следующие ключевые моменты:
Посмотрим, что предлагает альтернативная ООП организация, на базе прототипов.
Здесь основным понятием являются динамические изменяемые (мутируемые) объекты (dynamic mutable objects).
Мутации (полная изменяемость: не только значений, но и всех характеристик) непосредственно связаны с динамикой языка.
Такие объекты могут самостоятельно хранить все свои характеристики (свойства, методы) и в классе не нуждаются.
object = {a: 10, b: 20, c: 30, method: fn};
object.a; // 10
object.c; // 30
object.method();
Более того, в виду динамики, они могут свободно изменять (добавлять, удалять, модифицировать) свои характеристики:
object.method5 = function () {...}; // добавили новый метод
object.d = 40; // добавили новое свойство "d"
delete object.c; // удалили свойство "с"
object.a = 100; // модифицировали свойство "а"
// в итоге: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};
То есть, при присвоении, если определённая характеристика не существует в объекте, она создаётся и инициализируется переданным значением; если существует, – производится её модификация.
Повторное использование кода в данном случае достигается не за счёт расширения классов (обратите внимание, ни о каких классах, как о множествах жёстких характеристик, речи не идёт; здесь их вообще нет), а посредством обращения к, так называемому, прототипу.
Прототип (Prototype) — это объект, служащий либо прообразом для других объектов, либо вспомогательным объектом (делегатом), к характеристикам которого может обратиться оригинальный объект, в случае, если сам оригинальный объект не обладает нужной характеристикой.
Прототипом для объекта может служить абсолютно любой объект, и, опять же, в виду мутаций, объект свободно может менять свой прототип – динамически, по ходу программы.
Я напомню, мы сейчас ведём разговор об общей теории, мало касаясь реализаций; когда будем разбирать конкретные реализации (и, в частности, ECMAScript), увидим ряд своих особенностей.
Пример (псевдокод):
x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototype]] = x; // x – прототип y
y.a; // 40, собственная характеристика
y.c; // 50, тоже собственная
y.b; // 20 – полученная из прототипа: y.b (нет) -> y.[[Prototype]].b (да): 20
delete y.a; // удалили собственную "а"
y.a; // 10 – получена из прототипа
z = {a: 100, e: 50}
y.[[Prototype]] = z; // изменили прототип y на z
y.a; // 100 – получена из прототипа
y.e // 50, тоже – получена из прототипа
z.q = 200 // добавили новое свойство в прототип
y.q // изменения отобразились и на y
Данный пример показывает важную особенность и механизм, связанный с прототипом, когда прототип выступает в качестве вспомогательного объекта, к характеристикам которого, в случае отсутствия собственных подобных характеристик, обращаются другие объекты.
Этот механизм называется делегацией (delegation), а связанная с ним прототипная модель, – делегирующим прототипированием.
Обращение к характеристикам в данном случае называется посылкой сообщения объекту. Т.е., когда объект не может ответить на сообщение самостоятельно, он обращается к своему прототипу (делегирует ему полномочия за ответ).
Повторное использование кода в данном случае называется делегирующим наследованием (delegation based inheritance) или наследованием, основанным на прототипах (prototype based inheritance).
Поскольку прототипом может быть любой объект, соответственно, и у прототипов, могут быть свои прототипы. Данная комбинация связанных между собой прототипных объектов образует, так называемую, цепь прототипов (prototype chain). Она, так же, как и в статичных классах, иерархична, однако, в виду мутаций может свободно перегруппировываться, изменяя иерархию и состав.
x = {a: 10}
y = {b: 20}
y.[[Prototype]] = x
z = {c: 30}
z.[[Prototype]] = y
z.a // 10
// z.a найдено по цепи прототипов:
// z.a (нет) ->
// z.[[Prototype]].a (нет) ->
// z.[[Prototype]].[[Prototype]].a (да): 10
Касательно ECMAScript, здесь используется именно эта реализация – делегирующее прототипирование. Однако, как мы увидим, на уровне стандарта и реализаций есть и свои особенности.
Итак, выделим ключевые моменты данной организации:
Объекты ECMAScript – полиморфны во многих отношениях.
К примеру, одна функция может быть применена к разным объектам, как, если бы, она являлась родной характеристикой объекта (в виду определения this на этапе вызова):
function test() {
alert([this.a, this.b]);
}
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
var a = 1;
var b = 2;
test(); // 1, 2