This Might Be Useful

ООП в JavaScript - правильное объявление классов, методов и свойств

Когда-нибудь каждый JavaScript-программист осознаёт, что JS — объектно-ориентированный язык, и от функционального программирования пытается перейти к ООП. И здесь его подстерегают некоторые опасности, происходящие от непонимания того факта, что JS — язык не классов (как Паскаль или Цэ-два-креста), а прототипов.

Так, уже многое написано о проблеме наследования (которого в JS нет). Я же постараюсь рассказать о менее освещённом, но едва ли не более важном подводном камне: грамотной реализации методов.

Программисты пытаются объявлять классы в привычной для них форме, из-за чего возникают утечки памяти и прочие неприятные вещи. На самом деле нужно всего лишь научиться использовать прототипы.

Эта статья предназначена прежде всего для начинающих JS-программистов.

Ниже я буду использовать понятие «класс» в том смысле, в каком оно понимается в Паскале или Цэ-двух-крестах; хоть в JS таких классов, вообще говоря, нет, однако кое-что весьма сходно по форме и смыслу.

С самого начала всем становятся известны две базовые вещи:

  1. класс описывается функцией-конструктором;
  2. методы являются свойствами-функциями.

Поэтому программисты начинают писать весьма естественно:

JavaScript:
  1. function Test(){
  2.   // объявляем и инициализируем свойства
  3.   this.x=5;
  4.   this.y=3;
  5.   // объявляем методы
  6.   this.sum=function(){
  7.     return this.x+this.y;
  8.   }
  9.   // выполняем иные конструктивные действия
  10.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  11. }

После этого вроде бы получается то, что мы и хотели: получается класс Test с двумя свойствами x (изначально 5) и y (изначально 3) и методом sum, вычисляющим сумму x и y. При конструировании выводится элёт с иксом, игреком и суммой.

Но что происходит на самом деле? При конструировании объекта Test каждый раз вызывается функция Test. И каждый раз она создаёт новую анонимную функцию и присваивает её свойству sum! В результате в каждом объекте создаётся свой, отдельный метод sum. Если мы создадим сто объектов Test — получим где-то в памяти сто функций sum.

Очевидно, так делать нельзя. И важно это осознать как можно скорее.

После понимания этого факта начинающие программисты часто поступают следующим образом: создают отдельно функцию sum, а в конструкторе её присваивают свойству:

JavaScript:
  1. function Test(){
  2.   // объявляем и инициализируем свойства
  3.   this.x=5;
  4.   this.y=3;
  5.   // прикручиваем методы
  6.   this.sum=Test_sum;
  7.   // выполняем иные конструктивные действия
  8.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  9. }
  10.  
  11. // реализуем методы
  12. function Test_sum(){
  13.   return this.x+this.y;
  14. }

В результате, действительно, функция Test_sum создаётся только один раз, а при каждом конструировании нового объекта Test создаётся только ссылка sum.

В то же время это малограмотный вариант. Всё можно сделать гораздо красивее и правильнее, используя самую основу JavaScript - прототипы:

JavaScript:
  1. function Test(){
  2.   // объявляем и инициализируем свойства
  3.   this.x=5;
  4.   this.y=3;
  5.   // выполняем иные конструктивные действия
  6.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  7. }
  8.  
  9. // объявляем методы
  10. Test.prototype.sum=function(){
  11.   return this.x+this.y;
  12. }

Мы создаём свойство sum не класса Test, а его прототипа. Поэтому у каждого объекта Test будет функция sum. Собственно, на то он и прототип, чтобы описывать вещи, которые есть у каждого объекта. Более того, обычные, не функциональные, свойства тоже было бы логично загнать в прототип:

JavaScript:
  1. function Test(){
  2.   // выполняем иные конструктивные действия
  3.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  4. }
  5.  
  6. // объявляем, инициализируем, реализуем свойства и методы
  7. Test.prototype.x=5;
  8. Test.prototype.y=3;
  9. Test.prototype.sum=function(){
  10.   return this.x+this.y;
  11. }

Плохо здесь то, что объявления свойств и методов идут после их использования в конструкторе. Но с этим придётся смириться…

Ещё здесь неприятно многократное повторение Test.prototype. К счастью, JS — это не Цэ-два-креста, и у нас есть замечательное предложение this. Впрочем, нас подстерегает неприятный сюрприз: нижеследующий вариант не работает.

JavaScript:
  1. function Test(){
  2.   // выполняем иные конструктивные действия
  3.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  4. }
  5.  
  6. // объявляем, инициализируем, реализуем свойства и методы
  7. with(Test.prototype){
  8.   x=5;
  9.   y=3;
  10.   sum=function(){
  11.     return this.x+this.y;
  12.   }
  13. }

Почему не работает — загадка. Как ни крути, а слово prototype придётся повторять:

JavaScript:
  1. function Test(){
  2.   // выполняем иные конструктивные действия
  3.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  4. }
  5.  
  6. // объявляем, инициализируем, реализуем свойства и методы
  7. with(Test){
  8.   prototype.x=5;
  9.   prototype.y=3;
  10.   prototype.sum=function(){
  11.     return this.x+this.y;
  12.   }
  13. }

Явное преимущество здесь в группировании объявлений всей начинки класса Test в один блок — за исключением остающегося осторонь конструктора. Но и с этим можно справиться, если вспомнить, что функцию можно объявить через минимум три синтаксиса:

JavaScript:
  1. with(Test=function(){
  2.   // выполняем иные конструктивные действия
  3.   alert("Constructor: x="+this.x+", y="+this.y+", sum="+this.sum());
  4. }){
  5.   // объявляем и инициализируем свойства
  6.   prototype.x=5;
  7.   prototype.y=3;
  8.   // объявляем методы
  9.   prototype.sum=function(){
  10.     return this.x+this.y;
  11.   }
  12. }

В результате получается почти та естественная запись, с которой мы начали, разве что слово this заменили на prototype; ну и переместили в начало «иные конструктивные действия» — как я уже сказал, с этим, к сожалению, придётся смириться.

Впрочем, если от конструктора ничего, кроме создания свойств и методов, не требуется, получается и вовсе красота:

JavaScript:
  1. with(Test=new Function){
  2.   // объявляем и инициализируем свойства
  3.   prototype.x=5;
  4.   prototype.y=3;
  5.   // объявляем методы
  6.   prototype.sum=function(){
  7.     return this.x+this.y;
  8.   }
  9. }

Как видим, приложив лишь небольшие усилия по переосмыслению основ ООП в JS, можно научиться грамотно объявлять классы.

Источник статьи: Хабрахабр / Web-разработка

· Как скачать видеоролик с YouTube в формате mp4
· Динамическое создание обработчиков событий
· Избавляемся от самопроизвольно появляющейся Error Console в Firefox
· Сброс визуальных настроек Windows Explorer
· Преобразование даты в unix timestamp и обратно

- Коментировать
- Trackback

2 Responses to “ООП в JavaScript - правильное объявление классов, методов и свойств”


  1. d1pr3d Says:

    Ммм, полезно и вовремя!

  2. Sun Says:

    так помоему тоже ничего :)
    Test.prototype={
    x:5,
    y:3,
    sum:function(){
    return this.x+this.y;
    }
    }

Leave a Reply