ООП в прототипном стиле JavaScript
Как мы знаем, в JavaScript есть два вида наследования:
— ООП в функциональном стиле;
— ООП в прототипном стиле;
Более популярным является прототипный стиль, поскольку имеет главное преимущество — методы в прототипе автоматически доступны везде и всегда. Почему? Методы записываются в объекте, ссылка на который находится в специальном свойстве prototype. Такое свойство есть у каждой функции — оно ссылается на объект с одним единственным свойством constructor:
1 2 3 4 | function F() {}; F.prototype = { constructor: F }; |
Объекты-прототипы есть и у встроенных конструкторов (Object.prototype, Array.prototype и т.д) — в них хранятся служебные методы, например, toString, join, прочие.
Мы также всегда можем добавить свои методы в объект конструктор, например:
1 2 | Object.prototype.countSomething = function() {...} F.prototype.countSomething = function() {...} |
И эти методы станут доступны новым объекту, который создаст конструктор F:
1 2 3 4 | function F() {}; F.prototype.countSomething = function() {...}; var f = new F(); f.countSomething(); // will work! |
Каким образом?
Свойство F.prototype буквально означает, что при запуске конструктора F будет создан новый объект, который получит ссылку f.__proto__ на объект-прототип со всеми его методами:
Вот и получается, что:
1 2 3 | alert(f.__proro__ == F.prototype) // true; alert(f.countSomething == F.prototype.countSomething); // true alert(f.__proro__. countSomething == F.prototype.countSomething); // true |
Обратите внимание на последние 2 строки: если метод countSomething() не нашёлся в самом объекте f, созданным конструктором F, поиск продолжается в его объекте-прототипе (f.__proro__).
Пойдём дальше и рассмотрим наследование конструкторов один от другого. Пускай, у нас есть 2 конструктора: Second будет наследовать от First:
1 2 3 4 5 | function First() {...}; First.prototype = {...}; // здесь общие методы в объекте-прототипе function Second() {...}; Second.prototype = {...}; // здесь конкретные методы в объекте-прототипе var obj = new Second(); |
Алгоритм наследования такой: если нужный метод не найдёт в объекте obj, мы ищем в Second.prototype (объекте-прототипе конструктора, который создал obj); если нужного метода и там нет, ищем в First.prototype:
obj > Second.prototype > First.prototype.
Как мы уже знаем, при создании новый объект obj автоматически получает ссылку на объект-прототип: obj.__proto__ == Second.prototype. А как же заставить Second.prototype наследовать от First.prototype?
Можно, конечно, прописать такую конструкцию: Second.prototype.__proto__ = First.prototype, но на самом деле есть специальный метод:
1 | Second.prototype = Object.create(First.prototype); |
Он создаёт новый пустой объект с ссылкой на объект-прототип First.prototype. Далее мы смело можем добавлять нужные нам методы в Second.prototype. Итоговая последовательность наследования:
1 2 3 4 5 6 | function First() {...}; // объявили общий конструктор First.prototype = {...}; // добавили методы в его объект-прототип function Second() {...}; // объявили конкретный конструктор Second.prototype = Object.create(First.prototype); // создали пустой объект с ссылкой на прототип First.prototype Second.prototype = {...}; // добавили методы в его объект-прототип var obj = new Second(); // запустили конструктор |