Данный паттерн используется во фреймворках, таких как Google Closure Library. Нативные объекты JavaScript также используют данный паттернт.
Объявление Pseudo-class
Термит Pseudo-class выбран потому, что в JavaScript нет как таковых классов, как в других языка как C, Java, PHP и др., но данный паттерн близок к определению класса.
Псевдо-класс состоит из функции конструктора и методов.
Например, псевдо-класс Animal состоит за одного метода sit и 2 свойств:
function Animal(name) { this.name = name } Animal.prototype = { canWalk: true, sit: function() { this.canWalk = false alert(this.name + ' sits down.') } } var animal = new Animal('Pet') // (1) alert(animal.canWalk) // true animal.sit() // (2) alert(animal.canWalk) // false
- Когда вызывается
new Animal(name), объект получается ссылку__proto__наAnimal.prototype(см. правую часть схемы). - Метод
animal.sitизменяетanimal.canWalkв экземпляре, поэтому наше животное больше не может ходить, а другие могут.
Схема псевдо-класса:
- Методы и свойства по умолчанию определяются в прототипе.
- Методы в
prototypeиспользуютthis, который указывает на текущий объект так как значениеthisзависит от контекста вызова. Поэтому вanimal.sit()thisотноситься кanimal.
Наследование
Давайте создадим новый класс, который будет наследоваться от Animal, например Rabbit:
function Rabbit(name) { this.name = name } Rabbit.prototype.jump = function() { this.canWalk = true alert(this.name + ' jumps!') } var rabbit = new Rabbit('John')
Как видим, кролик имеет такую же структуру как и Animal — метод определен в прототипе.
Для наследования от Animal, необходимо сделать так Rabbit.prototype.__proto__ == Animal.prototype. Это естественное требование, так как если метод не найден в Rabbit.prototype, то его будем искать в методах родителя Animal.prototype.
Вот так, например:

Чтобы реализовать это, мы должны создать сначала пустой объект Rabbit.prototype наследуемы от Animal.prototype и после этого добавить методы.
function Rabbit(name) { this.name = name } Rabbit.prototype = inherit(Animal.prototype) Rabbit.prototype.jump = function() { ... }
Где inherit создает пустой объект с указанным __proto__:
function inherit(proto) { function F() {} F.prototype = proto return new F }
Вот что получилось в конце:
// Animal function Animal(name) { this.name = name } // Animal methods Animal.prototype = { canWalk: true, sit: function() { this.canWalk = false alert(this.name + ' sits down.') } } // Rabbit function Rabbit(name) { this.name = name } // inherit Rabbit.prototype = inherit(Animal.prototype) // Rabbit methods Rabbit.prototype.jump = function() { this.canWalk = true alert(this.name + ' jumps!') } // Usage var rabbit = new Rabbit('Sniffer') rabbit.sit() // Sniffer sits. rabbit.jump() // Sniffer jumps!
Не используйте new Animal для наследования
Хорошо известный, но не правильный способ наследования, это когда вместо Rabbit.prototype = inherit(Animal.prototype) люди делают следующее:
// inherit from Animal Rabbit.prototype = new Animal()
Как результат, мы получаем new Animal в прототипе. Наследование работает, так как new Animal естественно, наследует Animal.prototype.
… Но кто сказал, что new Animal() может быть вызван без name? Конструктор может строго требовать аргументов и умереть без них.
На самом деле, проблеме более концептуальная чем эта. Мы не хотим создавать Animal. Мы всего лишь хотим наследовать от него.
Вот почему Rabbit.prototype = inherit(Animal.prototype) более предпочтителен. Аккуратный наследство без побочных эффектов.
Вызов конструктора суперкласса
Конструктор суперкласса теперь вызывается автоматически. Мы можем вызвать его ручками с помощью Animal.apply() для текущего объекта:
function Rabbit(name) { Animal.apply(this, arguments) }
Данный код исполняет конструктор Animal в контексте текущего объекта и он задает name экземпляра.
Переопределение методов (полиморфизм)
Чтобы переопределить родительский метод, замените его в дочернем прототипе:
Rabbit.prototype.sit = function() { alert(this.name + ' sits in a rabbity way.') }
При вызове rabbit.sit() sit ищется по цепочке rabbit -> Rabbit.prototype -> Animal.prototype и находит его в Rabbit.prototype не доходя до Animal.prototype.
Конечно, мы может переопределить его иначе — напрямую в объекте:
rabbit.sit = function() { alert('A special sit of this very rabbit ' + this.name) }
Вызов родительского метода после переопределения
После переопределения метода, нам по-прежнему может понадобиться вызвать метод родителя. Это возможно если мы напрямую обратимся к прототипу родителя.
Rabbit.prototype.sit = function() { alert('calling superclass sit:') Animal.prototype.sit.apply(this, arguments) }
Все родительские методы вызываются с помощью apply/call куда передается текущий объект как this. Простой вызов Animal.prototype.sit() будет использовать Animal.prototype как this.
Sugar: removing direct reference to parent
В предыдущем примере, мы вызывали родительский класс напрямую. Как конструктор: Animal.apply..., или метод: Animal.prototype.sit.apply....
На самом деле, мы не должны делать этого. При рефракторинге может быть изменено имя либо добавлен промежуточных класс в иерархии.
Обычно, языки программирования позволяют вызвать родительские методы с помощью специальный ключевых слов, как, например, parent.method() или super().
Но в JavaScript такого нет, но мы может смоделировать это.
Следующая функция расширяет наследование и также задает родителя и конструктор без прямой ссылки на него:
function extend(Child, Parent) { Child.prototype = inherit(Parent.prototype) Child.prototype.constructor = Child Child.parent = Parent.prototype }
Вот так вот можно использовать:
function Rabbit(name) { Rabbit.parent.constructor.apply(this, arguments) // super constructor } extend(Rabbit, Animal) Rabbit.prototype.run = function() { Rabbit.parent.run.apply(this, arguments) // parent method alert("fast") }
В результате, мы можем переименовать Animal или создать промежуточных класс GrassEatingAnimal и изменения затронут только Animal и extend(...).
Приватные и защищенные методы (инкапсуляция)
Защищённые (protected) методы и свойства поддерживаются с договоренностью об именование. Методы, начинающиеся с подчеркивания ‘_’, не должны вызваться из вне (на деле могут).

Приватные (private) методы не поддерживаются.
Статические (static) методы и свойства
Статические методы и свойства определяются в конструкторе:
function Animal() { Animal.count++ } Animal.count = 0 new Animal() new Animal() alert(Animal.count) // 2
Итого
Вот и наш супер-мега-ООП фреймворк:
function extend(Child, Parent) { Child.prototype = inherit(Parent.prototype) Child.prototype.constructor = Child Child.parent = Parent.prototype } function inherit(proto) { function F() {} F.prototype = proto return new F }
Использование:
// --------- the base object ------------ function Animal(name) { this.name = name } // methods Animal.prototype.run = function() { alert(this + " is running!") } Animal.prototype.toString = function() { return this.name } // --------- the child object ----------- function Rabbit(name) { Rabbit.parent.constructor.apply(this, arguments) } // inherit extend(Rabbit, Animal) // override Rabbit.prototype.run = function() { Rabbit.parent.run.apply(this) alert(this + " bounces high into the sky!") } var rabbit = new Rabbit('Jumper') rabbit.run()
В наш фреймворк можно добавить чуток сахарку, например, функцию, которая копирую свойства из одного объекта в другой:
mixin(Animal.prototype, { run: ..., toString: ...})
Но на самом деле вам не очень-то и нужно использовать этот ООП паттерн. Всего лишь две функции справятся с этим.
ссылка на оригинал статьи http://habrahabr.ru/post/210596/
Добавить комментарий