Блог Сергея Байдачного

Мой блог о технологиях

Зарисовки по JavaScript для .NET разработчиков: Конструкторы и наследование

2 комментария

В этой статье мне хотелось бы освятить различные варианты наследования в JavaScript. Но прежде чем сделать это, давайте рассмотрим некоторые особенности, которые доступны в функциях-конструкторах, чтобы закрыть вопросы, связанные с созданием объектов.

Первое, на чем мне хотелось бы остановиться, это возвращаемое значение функции-конструктора. В языке С# и других, конструкторы не имеют возвращаемого значения и, создавая объект некоторого типа, Вы всегда получаете желаемое. В JavaScript функции-конструкторы, напротив, возвращают значение. В обычном случае это ссылка this, ссылающаяся на созданный объект. Последняя генерируется в начале работы конструктора и возвращается неявно (хотя можно вернуть и явно), но, если Вы решили изменить тип возвращаемого объекта, то это легко сделать, создав внутри что-то другое и вернуть объект, используя оператор return. Вот небольшой пример:

 

   1:  function Employee() {
   2:  return new Person();
   3:  }
   4:   
   5:  function Person() {
   6:  this.firstName = "Sergiy";
   7:  this.lastName = "Baydachnyy";
   8:  };
   9:   
  10:  var obj = new Employee();
  11:   
  12:  alert(obj.firstName);

 

Используя эту возможность, можно реализовать обработку ошибки, которая возникнет, если программист вызовет функцию-конструктор как обычную функцию (то есть без оператора new):

 

   1:  function Person() {
   2:  if (!(this instanceof Person)) {
   3:     return new Person();
   4:  }
   5:  this.firstName = "Sergiy";
   6:  this.lastName = "Baydachnyy";
   7:  }
   8:   
   9:  var obj = Person();
  10:   
  11:  alert(obj.firstName);

 

Теперь, когда мы окончательно разобрались с конструкторами, перейдем к возможным вариантам реализации наследования.

Вызов конструктора базового класса.

Мой любимый способ наследования, это вызов конструктора «базового класса» в конструкторе «производного класса». Иными словами, создавай объект с помощью функции-конструктора, Вы инициируете вызов другой (других) функции-конструктора, передавая текущий контекст. При этом инициализация последней выполняется без использования оператора new (так как он создает новый контекст), а прямым вызовом функции. В этом случае, создаваемый объект получает все данные и методы, объявленные в вызываемой функции-конструкторе. Посмотрим на примере:

 

   1:  function Person(fName, lName) {
   2:  this.firstName = fName;
   3:  this.lastName = lName;
   4:  this.Name = (function () { return this.firstName + " " + this.lastName });
   5:  }
   6:   
   7:  function Developer(fName, lName, lang) {
   8:  Person.call(this,fName, lName);
   9:  this.language = lang;
  10:  }
  11:   
  12:  var d = new Developer("Sergiy", "Baydachnyy", "C#");
  13:   
  14:  alert(d.Name());

 

Из кода видно, что мы используем метод call для установки контекста функции-конструктору. Аналогично можно было бы использовать и метод apply. Данный подход мне нравится тем, что позволяет получить все свойства и методы наследуемого объекта, с возможностью их вызова внутри самого конструктора.

Между тем у этого подхода есть несколько недостатков:

· Методы, которые объявляются в прототипах, никоим образом не попадают в производный объект. Иными словами в производный объект попадает все, что объявлено с помощью this;

· Определить связь с наследуемым объектом нет никакой возможности. Иными словами instanceof полностью работать не будет;

· Поскольку методы в JavaScript также являются объектами и занимают какую-то память, то эффективнее использовать ссылку на метод вместо его дублирования.

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

Использование прототипа.

Метод, основанный на использовании прототипов, состоит в том, что в свойство prototype производного объекта устанавливается ссылка на базовый объект (созданный с помощью new). В этом случае производный объект получает доступ ко всем полям базового объекта. При этом цепочка наследований может быть сколь угодно длинной.

 

   1:  function Person(fName, lName) {
   2:  this.firstName = fName;
   3:  this.lastName = lName;
   4:  this.Name = (function () { return this.firstName + " " + this.lastName });
   5:  }
   6:   
   7:  function Developer(lang, fName, lName) {
   8:  this.language = lang;
   9:  this.firstName = fName;
  10:  this.lastName = lName;
  11:  this.constructor = Developer;
  12:  }
  13:   
  14:  Developer.prototype = new Person();
  15:  var d = new Developer("C#", "Sergiy", "Baydachnyy");
  16:   
  17:  alert(d.Name());

 

Обратите внимание, что этот код не только устанавливает свойство prototype, но и изменяет свойство constructor, которое меняется по ходу установки prototype.

Этот подход также обладает рядом недостатков:

· Установку прототипа нужно выполнять вне функции-конструктора. Это означает, что мы не сможем воспользоваться методами базового объекта внутри конструктора производного;

· Поскольку при инициализации прототипа мы задаем шаблон для всех будущих объектов, нет смысла использовать параметры в конструкторе базового объекта. Данные нужно дублировать, в противном случае они будут общими для всех объектов. Мы можем даже написать код, который удаляет лишние поля данных из прототипа;

· Изменение прототипа скажется на всех объектах, включая производные;

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

Комбинация двух методов.

Часто, чтобы перенести данные и методы базового объекта в производный, сохранив при этом цепочку прототипов, прибегают к комбинации обоих методов. То есть, в конструкторе производного объекта вызывают функцию-конструктор базового (в текущем контексте), а далее устанавливают прототипы. Подобный подход нас практически избавляет от недостатков первого метода, но недостатки второго метода реализации наследования, остаются.

Наследование в ECMA Script 5

Существует еще один механизм наследования, о котором я не говорил, это наследование через прототип. В этом случае мы прототипу одного объекта присваиваем прототип другого объекта (иногда через создание промежуточной функции с пустым прототипом). А затем расширяем объект по своему усмотрению.

Мне этот метод не особо нравится, так как он также не позволяет красиво группировать код. В тоже время он существует и ECMA Script 5 определяет специальный метод у Object, который создает объект наследник через прототип:

 

   1:  function Person(fName, lName) {
   2:  this.firstName = fName;
   3:  this.lastName = lName;
   4:  Person.prototype.Name = (function () { return this.firstName + " " + this.lastName });
   5:  }
   6:   
   7:  var s = Object.create(new Person("Sergiy", "Baydachnyy"));
   8:   
   9:  //теперь можно расширять созданный объект

 

В завершении отмечу, что «тупое» копирование ссылок на методы базового объекта также является вариантом наследования. В зависимости от задачи, Вы легко можете комбинировать различные методы.

Вот, например, интересный код, который мне пришел в голову:

 

   1:  function Person(fName, lName) {
   2:  this.firstName = fName;
   3:  this.lastName = lName;
   4:  Person.prototype.Name = (function () { return this.firstName + " " + this.lastName });
   5:  }
   6:   
   7:  function Developer(lang, fName, lName) {
   8:   
   9:  function IDeveloper() {
  10:  }
  11:   
  12:  IDeveloper.prototype = new Person(fName,lName);
  13:  var that = new IDeveloper();
  14:  that.language = lang;
  15:  that.constructor = Developer;
  16:  return that;
  17:  }
  18:   
  19:  var d = new Developer("C#", "Sergiy", "Baydachnyy");

 

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

Реклама

Written by Sergiy Baydachnyy

29.08.2011 в 18:24

Опубликовано в Internet Explorer 10, Internet Explorer 9, JavaScript

Tagged with

комментария 2

Subscribe to comments with RSS.

  1. Спасибо. Вы очень мне помогли. Материал изложен коротко и ясно. Так держать!

    Kinoman

    24.09.2011 at 05:07

  2. В javascript наследование немного отличается от стандартного ООП, да оно основано на прототипах. Если кратко то так:
    есть функция-конструктор Person
    function Person (name_p,family_p) {
    this.name=name_p; // свойство name
    this.family=family_p; // свойство family
    }
    создаем объект с помощью Person
    var p1=new Person(«Vladik»,»Ivanov»)
    теперь создадим объект наследник от p1, для этого создадим новую функцию конструктор Person_debt
    function Person_debt (date_debt, amount_debt) {
    this.date=date_debt; // свойство «дата взятия долга»
    this.amount=amount_debt; // свойство «сумма долга»
    }
    САМОЕ ВАЖНОЕ, укажем что прототип для данной функции-конструктора будет p1, т.е. все объекты от Person_debt будут наследниками p1
    Person_debt.prototype=p1;
    И создаем два объекта наследника
    var p_debt=new Person_debt(«01.01.2013″,»30 000rub»);
    var p_debt2=new Person_debt(«01.01.2013″,»30 000rub»);
    Они унаследует свойства родителя p1.

    Понятно разъяснено здесь по поповоду наследования http://s-engineer.ru/category/struktura-i-sintaksis-javascript/


Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: