После прохождения теоретической части пришло письмо об окончании теоретической части курса, будет ли практическая часть? |
Наследование и замыкание
В "Основы объектно-ориентированного программирования" были рассмотрены основы объектно-ориентированного программирования в JavaScript. В данной лекции эта тема будет продолжена рассмотрением методов наследования, а также полезных (и опасных) свойств замыкания.
Наследование
В "Основы объектно-ориентированного программирования" была создана функция 'Cat':
function Cat(name){ this.name = name; } Cat.prototype = { species: 'Cat', talk: function(){ alert('Meow!'); }, callOver: function(){ alert(this.name+' ignores you'); }, pet: function(){ alert('Pet!'); } }
Теперь можно создать любое количество котов, но как быть, если мы захотим создать объект другого типа, например, собаку? В этом случае понадобится создать совершенно новую функцию, со своими собственными прототипами. Если два объекта используют одни и те же функции (например, можно было бы добавить функции sleep (спать), eat (есть), и play (играть)), то в результате мы бы имели чрезмерное дублирование кода. Решением является концепция наследования.
По сути наследование позволяет определить объекты "предки" и "потомки". Потомок наследует все свойства своего предка. Можно было создать, например, функцию Animal, Pet или Mammal. Обе функции Cat и Dog обладали бы многими свойствами функции предка Animal, и нам пришлось бы писать этот код один раз.
Проблема в том, что JavaScript не имеет в действительности встроенного механизма наследования, поэтому эту функциональность необходимо создавать самостоятельно. Для этого существует несколько различных способов. Один из них состоит в использовании функции call. Эта функция позволяет вызывать одну функцию из контекста другой, т.е. мы можем определить, как действует ключевое слово this. С помощью call можно написать класс Animal (Животное), а затем вызвать его из класса Cat или Dog.
function Animal(name){ this.name = name; this.species = 'Animal'; this.sleep = function(){ alert(this.name+' спит: Хрррр'); } } function Cat(name){ Animal.call(this, name); this.talk = function(){ alert('Мяу!'); } } function Dog(name){ Animal.call(this, name); this.talk = function(){ alert('Гав!'); } } var sam = new Cat('Sam'); var joe = new Dog('Joe'); sam.sleep(); // Sam спит: Хрррр joe.sleep(); // Joe спит: Хрррр sam.talk(); // Мяу! joe.talk(); // Гав!
Хотя это работает, мы немного ограничены в своих возможностях. Например, прототипирование не действует при использовании этого метода: все прототипы, заданные на Animal, не будут переноситься в функции Cat или Dog. Как мы знаем из "Основы объектно-ориентированного программирования" , определенные внутренне с помощью " this." функции создают новый экземпляр всякий раз при создании новой копии предка. В этом случае всякий раз при создании функции Animal, Cat или Dog появляется новая копия функций species и sleep. Как можно догадаться, это не самый эффективный способ.
Лучшим подходом является прототипирование всего родительского класса на классе-потомке. Это предоставляет доступ ко всем свойствам и методам класса предка:
function Animal(name){ this.name = name; } Animal.prototype = { species: 'Animal', sleep : function(){ alert(this.name+' спит: Хрррр'); } } function Cat(name){ Animal.apply(this, arguments); } Cat.prototype = new Animal; Cat.prototype.species = 'Cat'; Cat.prototype.talk = function(){ alert('Мяу!'); } function Dog(name){ Animal.apply(this, arguments); } Dog.prototype = new Animal; Dog.prototype.talk = function(){ alert('Гав!'); } var sam = new Cat('Sam'); var joe = new Dog('Joe'); sam.sleep(); // Sam спит : Хрррр joe.sleep(); // Joe спит: Хрррр alert(sam.species); // Cat alert(joe.species); // Animal - для Dog функция species не определена
Можно продолжить это дальше и создать отдельные функции для различных пород собак или кошек и т.д.