В традиционном ООП класс рассматривается как некий чертёж или структурная схема будущего объекта, а экземпляр является воплощением класса, набором реальных данных и кода, составляющих конкретный объект. Идею наследования в традиционном классовом ООП можно представить как передачу чертежей от предков к потомкам с возможностью изменений, которые вносят в чертежи потомки. При создании объекта каждый потомок строит свой собственные конгломерат данных и кода на основании самой актуальной версии имеющихся у него чертежей (описания класса).
Каждый объект при создании получает внутреннее свойство [[Prototype]], являющееся ссылкой на объект, свойства и методы которого используются в том случае, если они не находятся у оригинального объекта. (В ряде браузеров это свойство можно менять напрямую (__proto__), но такой подход не рекомендуется). Существует целая цепочка ссылок, по которой может производиться поиск вплоть до того объекта, для которого [[Prototype]] установлен в null (такой объект можно считать корневым). В результате напрашивается следующая аналогия. В традиционном ООП каждый класс-потомок можно сравнить с набором чертежей исправленных и дополненных по ходу наследования класса. А каждый экземпляр объекта является копией, построенной по этой версии чертежей. В прототипном ООП каждый объект является законченной сущностью, а наследование предполагает заимствование методов и свойств у уже существующих объектов.
Прототип задается объекту двумя способами:
1. Напрямую в качестве параметра вызова Object.create():
Cat = { name: 'Itchy', tail: true,
voice: function() { return "Meow" };
var obj1 = Object.create(Cat)
2. Через переопределение свойства __proto__ у наследующего объекта:
var obj2 = {};
obj2.__proto__ = Cat;
Второй способ поддерживается не всеми браузерами и не рекомендуется к использованию.
В обоих случаях объект получает "в распоряжение" свойства и методы прототипа, но они не являются его собственными свойствами:
console.log(Object.getOwnPropertyNames(obj1)); // > []
console.log(Object.getOwnPropertyNames(obj2)); // > []
console.log(obj1.name); // > Itchy
console.log(obj2.voice()); // > Meow
Прототип задается объекту двумя способами:
1. Напрямую в качестве параметра вызова Object.create():
Cat = { name: 'Itchy', tail: true,
voice: function() { return "Meow" };
var obj1 = Object.create(Cat)
2. Через переопределение свойства __proto__ у наследующего объекта:
var obj2 = {};
obj2.__proto__ = Cat;
Второй способ поддерживается не всеми браузерами и не рекомендуется к использованию.
В обоих случаях объект получает "в распоряжение" свойства и методы прототипа, но они не являются его собственными свойствами:
console.log(Object.getOwnPropertyNames(obj1)); // > []
console.log(Object.getOwnPropertyNames(obj2)); // > []
console.log(obj1.name); // > Itchy
console.log(obj2.voice()); // > Meow
Изменение свойства, полученного у прототипа, меняет его только у наследника. При этом создается новое свойство, принадлежащее наследнику и "перекрывающее" базовое:
obj1.name = "Tom";
console.log(obj1.name); // > Tom
console.log(obj2.name); // > Itchy
Свойство наследника может быть удалено (если его атрибут changable не установлен в false). После этого наследник получает доступ к свойству прототипа:
delete obj1.name;
console.log(obj1.name); // > Itchy
Замена же свойства у объекта-прототипа меняет его и у всех наследников, для которых не определено собственное свойство:
Cat.name = "Jack";
console.log(obj1.name); // > Jack
console.log(obj2.name); // > Jack
Важно! [[Prototype]] и __proto__ представляют собой одно и то же свойство (первое свойство - внутренний элемент реализации, второе - интерфейс доступа, который предоставляют некоторые браузеры). Это свойство указывает на следующий элемент "заимствования" в цепочке наследования, где объект должен брать свойства и методы, если у него их нет. Оба этих свойства отличаются от свойства prototype, которое имеет каждый объект, унаследованный от Object.
Прототипы не позволяют проводить множественное наследование. За один раз создается только один объект, наследующий свойства и методы прототипа. Множественное наследование можно эмулировать программно при помощи функции, копирующей все свойства исходного объекта в целевой. Таким образом реализуются т.н. примеси (mixins). Более подробно примеси рассматриваются в отдельном посте.
function extend(target, source) {
Object.getOwnPropertyNames(source).forEach(function(key) {
Object.defineProperty(target,key,
Object.getOwnPropertyDescriptor(source, key)) })
return target
}
Прототип получается также двумя способами:
1. Как результат вызова функции Object.getPrototypeOf(object)
2. Чтением свойства __proto__ объекта.
КОНСТРУКТОР - способ создавать объекты с инициализацией в императивном стиле (в отличие от ранее рассмотренного декларативного). В качестве конструктора можно использовать любую функцию. Создание объекта происходит при вызове функции-конструктора с оператором new.
Любая функция, будучи объектом, имеет в качестве одного из унаследованных свойств свойство prototype, представляющее собой пустой объект ({}). При вызове функции в качестве конструктора внутреннее свойство [[Prototype]] объекта, создаваемого этим конструктором, получает ссылку на свойство prototype функции конструктора.
Последовательность действий при вызове конструктора с new:
1. Создать пустой объект {}, наследующий от Object.prototype
2. Установить в [[Prototype]] создаваемого объекта ссылку на prototype конструктора, чтобы унаследовать поведение прототипа. Установить в constructor создаваемого объекта ссылку на функцию конструктор.
3. Вызвать конструктор в контексте создаваемого объекта (this внутри конструктора будет ссылкой на создаваемый объект).
4. Если функция возвращает объект (любой), сделать его возвращаемым значением функции, иначе вернуть создаваемый объект.
Важно! Конструктор может возвращать произвольный объект, в том числе уже существующий, а не только создавать новый.
Комментариев нет :
Отправить комментарий