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

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

Зарисовки по JavaScript для .NET разработчиков: Объектно-ориентированное программирование

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

Немного мыслей

Недавно Виктор Шатохин пообещал много денег за .NET разработчика, знающего JavaScript. Я мысленно стал перебирать всех знакомых разработчиков с хорошим знанием .NET, и пришел к выводу, что они не соответствуют второму критерию (JavaScript). Не любим мы это дело. Язык кажется для нас примитивным и не стоящим внимания настоящего ООП программиста. А если добавить отсутствие хороших редакторов, проблемы с несовместимостью возможностей различных окружений JavaScript и невозможность защитить свою интеллектуальную собственность (ведь Ваш код легко можно скопировать), то тут же хочется плеваться от отвращения.

На самом деле многие тезисы, которые я изложил выше, могут быть и не верны, но именно такое понимание обычно формируется у .NET разработчиков. И это понимание я неоднократно высказывал. А ведь программировать на JavaScript мне практически и не довелось (как и многим из нас). Самое интересное то, что когда я высказывал свое мнение по этому поводу, никто даже не пытался мне сказать что-то противоположное. А на вопрос «кто любит JavaScript» обычно поднималось одна-две руки из зала.

Откуда же берется понимание о «недостойности» JavaScript? Я попробовал проанализировать эту проблему и пришел к выводу, что мнение формируется только на основе того кода, который мы видим на существующих сайтах. Кода, который, может быть и не всегда, написан верно. Я даже возьму на себя смелость заявить, что в большинстве случаев – совсем не верно. Но этот код формирует мнение.

А ведь я уже попадал в такую ловушку, еще во время моей учебы в университете, на факультете была очень популярна среда программирования Delphi. Используя эту среду, приложения мог разрабатывать любой (извините) идиот. Ведь в те далекие времена Visual Studio не предлагал визуальную среду разработки, а .NET Framework еще не существовал. Поэтому Delphi был отличной альтернативой для студентов, не сумевших освоить ООП, но владеющий мышкой. Приложения, которые сдавались, удивляли своей «правильностью». Данные часто хранились прямо в элементах управления, а о каких-то подходах ООП нельзя было и говорить. Естественно, в те далекие времена я писал код на С++, а Delphi вызывала лишь улыбку. Немного позже я узнал, что на Delphi можно писать отличные приложения и имеется множество коммерческих продуктов. Вот только сначала нужно получить знания по продукту, а потом приступать к разработке.

Именно поэтому в JavaScript я решил разобраться более детально, и был сильно удивлен. Так, еще три дня назад, я был уверен в том, что этот язык не заслуживает моего внимания, а сегодня я уверен в противоположном. JavaScript можно использовать для написания серьезных приложений. Другое дело, что для формирования альтернативного мнения мне пришлось прочитать четыре различных книги, посвященных JavaScript. Попутно я обнаружил, что ни одна книга не описывает все возможности языка, а иногда та или иная книга представляет какой-то из шаблонов (не всегда хороший), как единственно возможный. Но, пользуясь тем, что я все же ООП программист, мне удалось понять JavaScript настолько, чтобы сделать вывод: JavaScript – объектно-ориентированный язык программирования.

Мне еще предстоит разобраться с взаимодействием JavaScript с тем или иным окружение, с возможностями различных библиотек, а также определить список того, что из ECMA 5 уже поддерживается в IE9/10 и других браузерах, но это уже технические детали, а ниже я постараюсь изложить свою позицию, которая подтолкнула меня к изучению дополнительного материала по этому языку.

Отступление. Я вовсе не собираюсь претендовать на позицию, о которой говорил Виктор и имею другие побуждения в основе своих действий. Поэтому, если Вы тот самый разработчик, то пишите ВикторуJ

Зачем

Прежде чем перейти к ООП возможностям JavaScript давайте ответим на вопрос «Зачем?». Ведь сегодня JavaScript преимущественно используется для клиентских сценариев, где окружением выступает браузер. На самом деле JavaScript не зависит от платформы и не привязан к окружению. Иными словами, окружение «браузер» может заменять любое другое окружение, например «веб-сервер» или «операционная система». И сегодня уже есть множество разработок, позволяющих запускать JavaScript на серверной стороне. В то же время отсутствие привязки JavaScript к определенному производителю средств разработки, делает этот язык практически неуязвимым, ведь стратегия у отдельной компании могут меняться, а, следовательно, меняться и технологии.

Если же говорить о сегодняшних .NET и JavaScript разработчиках, то последних вряд ли можно убедить перейти на .NET, да и хлопотно это (много нового нужно учить). А вот .NET разработчику можно без труда освоить JavaScript, зная идеологию ООП. При этом у .NET разработчика больше шансов использовать «правильный» JavaScript, так как способ мышления давно уже сложился.

Этим я никоим образом не хочу обидеть JavaScript разработчиков и хочу отметить, что некоторые вещи (верстка, подходы при дизайне веб-интерфейсов и др.) .NET разработчикам не под силу. Но при разработке сложных приложений ООП разработчик имеет преимущество. А если к знаниям .NET прибавить и знание JavaScript, то это только повышает стоимость (с точки зрения зарплаты) такого специалиста. Поэтому ответ на вопрос «Зачем?» — больше возможностей, а, следовательно, большая заработная плата. (Если бы я знал такого разработчика, то уже бы смог заработатьJ)

Именно поэтому я решил окунуться в JavaScript и взглянуть на него с позиции .NET разработчика, чтобы попытаться понять, правильно ли я размышляю, так как совсем недавно я был не прав (опять же по моему мнению).

Переменные и функции

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

 

   1:  var val1 = "Hello";
   2:   
   3:  function SayHello() {
   4:     alert(val1);
   5:  }
   6:   
   7:  SayHello();

 

Какая гадость.

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

 

   1:  var val1 = "Hello";
   2:   
   3:  function SayHello() {
   4:     alert(window.val1);
   5:     //или
   6:     alert(window["val1"]);
   7:  }
   8:   
   9:  SayHello();

 

Обратите внимание, что выше я не очепятался, функция также является объектом. Причем именно на этом факте строится все ООП в JavaScript. Более того, в JavaScript все, кроме чисел, строк, логического типа (но для них есть обертки) является объектами.

Но вернемся к переменным, так как правила определения переменных нам понадобятся ниже. Как видно из кода выше, переменные определяются с помощью ключевого слова var. Если Вы определяете переменную внутри функции, то у нее область видимости вся функция, в противном случае это глобальная область видимости. Обращаю Ваше внимание на то, что если Вы что-то определяете без var, то оно попадает в глобальную область. Я не случайно написал «что-то» и «оно», поскольку в данном случае Вы объявили бы не переменную, а свойство – свойство глобального объекта, о чем мы поговорим в следующем блоке.

Нужно обратить внимание на то, что в JavaScript нет области видимости внутри блока, то есть переменную можно использовать внутри всей функции сразу после инициализации, независимо от блоков. Дурацкий пример, но работает:

 

   1:  function SayHello(i) {
   2:   
   3:  if (i>3)
   4:  {
   5:     var sHello="Hello";
   6:  }
   7:  alert(sHello);
   8:  }
   9:   
  10:  SayHello(4);

 

Переменной легко можно присваивать ссылку на функцию и использовать ее в дальнейшем:

 

   1:  var f1 = (function SayHello(i) {
   2:     if (i > 3) {
   3:     var sHello = "Hello";
   4:     }
   5:     alert(sHello);
   6:  });
   7:   
   8:  //или
   9:   
  10:  var f2 = f1;
  11:   
  12:  f1(4);
  13:  f2(5);

 

Следовательно, функции можно легко передавать как параметры другим функциям.

Наконец, если говорить о параметрах, то параметры никто не проверяет. Вы можете передавать в функцию любое количество параметров, а затем получать доступ к массиву их параметров, определяя их типы и количество. Таким образом, Вы можете создать «эмуляцию» перегрузки функций, определив несколько реализаций с разным количеством параметров, внутри одной функции.

Наконец, если у Вас есть вложенная функция, то она может легко использовать переменные и параметры родительской функции. Подобная функциональность называется замыканием и нам она очень пригодится при реализации некоторых механизмов ООП. Вот пример:

 

   1:  function generate(initialVal) {
   2:     return (function (mulVal) {
   3:        return initialVal * mulVal;
   4:     });
   5:  }
   6:   
   7:  var res = generate(2);
   8:  alert(res(2)); //4
   9:  alert(res(4)); //8

 

Последний пример немного интереснее? Как Вы видите, мы возвращаем ссылку на вложенную функцию, которая использует параметр, передаваемый родительской функции. Дальше мы рассмотрим еще несколько примеров и все станет ясно.

Рассмотрев несколько примеров с функциями, перейдем к ООП.

Создание объектов и инкапсуляция

Основными понятиями в ООП являются классы и объекты. Так вот, в JavaScript классов нет. Вы конструируете готовые объекты. Сказать, что это не правильно я не могу, так как в жизни именно так все и происходит: «Вы конструируете объект, а иногда прототип, из составных частей». Просто так, по описанию, ничего не происходит. Это только Бог сотворил Землю из хаоса. Видимо он использовал С++. Ну а мы, смертные,…

Так вот, чтобы создать примитивный (почти пустой) объект, можно воспользоваться следующим синтаксисом:

 

   1:  var obj1 = {};
   2:   
   3:  //или
   4:   
   5:  var obj2 = new Object();

 

Далее объект можно наполнить содержимым (функциями и свойствами). Это можно сделать в любой момент времени, расширяя объект по своему усмотрению:

 

   1:  var obj1 = {};
   2:   
   3:  obj1.val1 = "Hello";
   4:   
   5:  obj1.SayHello = (function () { return this.val1; });
   6:   
   7:  alert(obj1.SayHello());

 

Обратите внимание, что val1 является свойством объекта (данными). При этом, чтобы получить доступ к этим данным из функции объекта, необходимо использовать ключевое слово this, которое указывает на контекст объекта. Если не задать контекст, то свойство будет добавлено к глобальному объекту (это также произойдет, если использовать this в глобальной функции).

Добавив свойство или функцию (теперь уже метод) к объекту, их можно удалить в любой момент:

 

   1:  delete obj1.val1;
   2:   
   3:  delete obj1.SayHello;

 

Причем это также логично. Представьте себе, что Вы создали класс «Человек», наклепали конкретные объекты, а потом появилось устройство «мобильный телефон» и Вы хотите его добавить в класс. А ведь уже поздно, поезд уехал. Между прочим завтра, может понадобится убрать свойство «мобильный телефон» и сразу вживить чип в голову (то есть изрядно покопаться в объектах). В С++ Вы можете пытаться сделать универсальный мега-класс, предусматривающий все возможности. Но последнее (предусмотреть) не всегда получается. А в JavaScript раз и готово.

Все это здорово до тех пор, пока у нас создаются разные объекты. А что же делать, если мы решили заселить Землю людьми. Нужно ли нам дублировать код каждый раз? И вот тут на помощь приходят функции. Как я писал выше, функции также представляют собой объекты. Вот пример функции, к которой мы добавили свойство и другую функцию:

 

   1:  function testF() {
   2:   
   3:  }
   4:   
   5:  testF.val1 = "Hello";
   6:   
   7:  testF.SayHello = (function () { return "Hello" });
   8:   
   9:  alert(testF.SayHello());

 

Особенность функции состоит в том, что к ней можно применить оператор new, который позволит создать новый объект по образу и подобию функции. Таким образом получается, что если мы определим функцию Human и наполним ее кодом, добавляющим к новому объекту данные и другие функции (методы), то получим функцию-конструктор. Вот оно, ООП!!! Рассмотрим пример:

 

   1:  function Human(firstName, lastName) {
   2:   
   3:     this.getFirstName = (function () { return firstName; });
   4:     this.getLastName = (function () { return lastName; });
   5:   
   6:     this.Age = 33;
   7:   
   8:     this.Name = (function () { return this.getFirstName() + " " + this.getLastName(); });
   9:   
  10:     Human.CopyHuman = (function (h) { return new Human(h.getFirstName(), h.getLastName()); });
  11:  }
  12:   
  13:  var h = new Human("Sergiy", "Baydachnyy");
  14:   
  15:  alert(h.Name());
  16:   
  17:  var hCopy = Human.CopyHuman(h);
  18:   
  19:  alert(hCopy.Name());

 

Ну посмотрите, чем не класс (особенно для C# разработчиков, где весь код находится внутри класса)? Тут мы с Вами объявили функцию Human, где, использовав замыкание, создали два метода для чтения переменных, передаваемых функции при инициализации объекта (О!), затем объявили поле с данным, почему-то присвоив ему значение 33, создали еще один метод, который использовал уже созданные нами функции. В завершении всего, мы создали «статическую» функцию, которая предназначена для копирования объектов и может быть доступна через имя нашей функции-конструктора («класса»).

При этом к созданным объектам можно добавить новые данные и методы (или удалить существующие).

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

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

 

   1:  function Human(firstName, lastName) {
   2:     this.getFirstName = (function () { return firstName; });
   3:     this.getLastName = (function () { return lastName; });
   4:   
   5:     Human.prototype.Age = 33;
   6:   
   7:     Human.prototype.Name = (function () { return this.getFirstName() + " " + this.getLastName(); });
   8:   
   9:     Human.CopyHuman = (function (h) { return new Human(h.getFirstName(), h.getLastName()); });
  10:  }

 

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

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

Human.prototype.inn = 0;

Хочешь узнать реальный код, приходи в налоговую и получи справкуJ Все как в реальной жизни! А С++ так не может.

Многие разработчики определяют функцию-конструктор пустым, а затем записывают код, который добавляет методы к прототипу. Я не считаю такой подход правильным, так как он выпадает из привычного мне ООП стиля (все определяем в классе). Код получается разбросанный по разным местам, нельзя использовать замыкание (private свойства в классе), а также могут возникнуть проблемы с наследованием. Но JavaScript позволяет все. Тут скорее язык можно упрекнуть за излишние возможности, но не за недостаток.

Наследование и полиморфизм

Перейдем теперь к вопросам наследования и полиморфизма.

Поскольку в JavaScript нет четкой типизации ссылок, то язык по умолчанию поддерживает полиморфизм (чем-то похоже на Java). А вот наследование, это отдельная история. Наследование не просто присутствует в JavaScript, но и может быть реализовано несколькими способами (молчу про версию ECMA 5, где есть специальные методы в Object).

Первый способ работает, если в базовом классе все данные и методы добавлены через ключевое слово this (без прототипа). В этом случае в производном классе можно вызвать конструктор базового (именно вызвать), используя один из двух специальных методов call или apply (они отличаются лишь набором параметров). Особенностью этих методов есть то, что они позволяют передать конструктору базового класса, контекст объекта, создаваемого от производного, что позволит тут же создать все методы и данные базового конструктора в производном объекте:

 

   1:  function Human(firstName, lastName) {
   2:     this.getFirstName = (function () { return firstName; });
   3:     this.getLastName = (function () { return lastName; });
   4:     
   5:     this.Age = 33;
   6:   
   7:     this.Name = (function () { return this.getFirstName() + " " + this.getLastName(); });
   8:   
   9:     Human.CopyHuman = (function (h) { return new Human(h.getFirstName(), h.getLastName()); });
  10:  }
  11:   
  12:  function Women(firstName, lastName, colorOfHead) {
  13:     Human.call(this, firstName, lastName);
  14:   
  15:     this.color = colorOfHead;
  16:  }
  17:   
  18:  var w = new Women("Jessica", "Alba", "Brune");
  19:   
  20:  alert(w.Name());

 

Именно подход выше соответствует стандартной практике наследования в С++ или С#.

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

Заключение

Поскольку я пишу уже девятую страницу текста, то пора закругляться. Поэтому я не буду показывать все подходы. Если будет время, то в других постах я постараюсь освятить каждую тему (и другие) более детально. Целью же этого поста было желание дать Вам понять, что JavaScript является полноценным ООП языком и при этом очень интересным. Обратите на него внимание. Причем начинайте с самого языка, а уже потом с каких-то библиотек (типа JQuery), которые привязаны к каким-то окружениям.

Рекомендуемая литература:

Стоян Стефанов – JavaScript. Шаблоны

Дэвид Флэнаган – JavaScript. Подробное руководство.

Джон Рейсинг – JavaScript: Профессиональные приемы программирования

Реклама

Written by Sergiy Baydachnyy

26.08.2011 в 08:32

Опубликовано в .NET Development, JavaScript

Tagged with

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

Subscribe to comments with RSS.

  1. Сергей, отличная статья, спасибо.
    Сам недавно занялся тем же — применением JavaScript и jQuery в .NET проекте.

    Alex Melnychenko

    27.08.2011 at 13:46

  2. Також на проекті використовую JavaScript/.NET, тому вона мені є досить корисною. Дякую :).

    Serhiy Shumakov

    28.08.2011 at 12:15

  3. […] Зарисовки по JavaScript для .NET разработчиков: Объектно-орие… […]

  4. В сети такое встретишь не часто. сПАСИБО

    andreys2

    16.12.2011 at 21:30


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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s

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