Статьи

Введение в чистый дизайн JavaScript

Несмотря на все усилия, потраченные на его замену чем-то более приличным (например,  Flex , DartSilverlight и т. Д.), Javascript по-прежнему остается предпочтительным языком для сценариев на стороне браузера. И учитывая огромное внимание, которое HTML5 направляет на браузер, он снова становится чрезвычайно популярным языком.

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

Javascript является мощным и поставляется с богатой экосистемой. Его главная проблема, однако, заключается в том, что синтаксис настолько разрешающий, что требуется осторожность, чтобы не заканчиваться кучей не поддерживаемого кода спагетти.

Цель этого поста — представить основные советы по дизайну, которые помогут содержать вещи в чистоте и порядке. Я надеюсь, что это будет полезно для разработчиков javascript, борющихся с дизайном кода, или для опытных дизайнеров, борющихся с javascript…

Используйте OO инкапсуляцию

Резюме : каждая концепция должна быть заключена в объект.

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

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

function User(login) {
  /////////////
  // members
  this.username = login ;
  this.pseudo;
  this.photo = new Photo();

  /////////////
  // public API
  this.updatePhoto = function (photoLink, photoCaption) {
    if (this.photo == undefined) {
      this.photo = new Photo();
    }
    this.photo.apply(photoLink, photoCaption);
  }
}

Так что экземпляры User могут быть просто созданы с помощью:

var paul = new User("paul");
var luc = new User("luc");
luc.updatePhoto(photoHost + "/users/luc/profile.jpg", "Mr Handsome");

При разработке типов важно руководствоваться следующим принципом: Роберт С. Мартин назвал  SRP: принцип единой ответственности (pdf) : каждый тип должен иметь только одну ответственность. SRP может быть обобщен для любого контейнера кода с любой степенью детализации (функция, тип, библиотека, файл, …), что дает концепцию разделения интересов .

Я обычно избегаю как можно больше наследования и полиморфизма в javascript. Эти концепции выполнимы (см. Главу «ОО» в Eloquent javascript ), но они, как правило, делают код менее обслуживаемым, потому что нам нечего предупреждать, когда подтип нарушает контракт своего родителя (за исключением, конечно, ошибок или тщательного тестирование, но модульное тестирование для проверок определения типа действительно напоминает мне код EJB2 декларации кода котельной плиты, и я дал себе обещания относительно этого…).

О ОО и javascript уже много написано. Например, Сухдхир Джонатан недавно опубликовал хорошее введение в объекты в javascript,  в котором он иллюстрирует использование ключевого слова this.

Избегайте загрязнения пространства имен глобальной области

Резюме : объедините все объявления внутри библиотек. Библиотеки должны быть единственной вещью, объявленной в глобальном масштабе.

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

Javascript не предоставляет механизм, специально разработанный для библиотек, но мы можем использовать для этой цели одноэлементные объекты, например:

var myShinylib = {
  makeMarmelade : function (listOfVegetables) {
    // do something relevant with those vegetables here..
  },

  makeJuice : function (oneFruit) {
// put here algo to make juice...
  }
};

Конструкторы также могут быть собраны внутри библиотек с точно таким же синтаксисом:

var someVegetables = {
  Tomato : function (origin, size, age) {
    // put here the constructo for Tomato
  },

  Potato : function (origin, size, age, kind) {
    // put here the constructo for Potato
  }
};

Теперь, когда эти конструкторы и другие функции хорошо упакованы, их можно просто вызвать следующим образом:

var oneTomato = new someVegetables.Tomato("Chile", 12, 2);
var onePotato = new someVegetables.Potato("Belgium", 2, 2, "Charlotte");
var somethingInteresting = myShinylib.makeMarmelade([oneTomato, onePotato]);

Недостаток (но который также является самой целью) состоит в том, что теперь мы должны ввести полностью определенное имя для чего угодно (например, someVegetables.Tomato вместо просто Tomato), но я считаю, что это справедливая цена, которую нужно заплатить за избежание конфликтов имен и повышение стойкости кода.

Следующий вопрос: как оформить содержимое этих библиотек? Наиболее разумный ответ — снова применить группу SRP: внутри деклараций одной библиотеки, которые имеют одну общую цель, которая обычно используется повторно и / или которая обычно меняет тогетер в случае рефакторинга.

Использовать много JS исходного файла во время разработки

Резюме : код JavaScript должен быть разбит на несколько файлов, возможно, многие из них.

Каждый файл javascript должен содержать одну логическую группу объявлений. Основное правило — определять одну библиотеку на файл, хотя может быть много веских причин сделать иначе.

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

Положитесь на MVC и DOM на стороне браузера

Резюме : следуйте шаблону MVC для кода на стороне браузера (в дополнение к другим MVC, которые вы, вероятно, также на стороне сервера) и используйте шаблоны для создания элементов HTML.

MVC — это хорошо известный шаблон графического интерфейса, который доступен во многих вариантах (модель 1, модель 2, MVVM, ..). Шаблонирование — это механизм для динамического создания представлений ( V в MVC) путем применения данных времени выполнения к общим шаблонам.

«Голый» движок javascript довольно бесполезен для любого из этих двух шаблонов, но существует несколько популярных библиотек, чтобы помочь нам. Вот общая конструкция MVC, на которую я сейчас полагаюсь, основанная на jquery  и knockout :

JavaScript MVC с Jquery и нокаутом

  • Среди многих других тонкостей,  JQuery предоставляет мощный по методу слушать DOM событие (щелчок мыши, Keypressed ..). Это работает даже для элементов HTML, которые еще не существуют на момент регистрации слушателя.
  • knockout  (он же KO) предоставляет автоматически обновляемый движок шаблонов HTML: он прослушивает изменения в наблюдаемых объектах KO  и автоматически обновляет все ранее созданные элементы DOM.
  •  KO также позволяет вам подписаться на события KO, поэтому для случаев, когда вы хотите обновить DOM самостоятельно, вы можете использовать этот механизм для регистрации «пользовательских обновлений GUI» (которые, возможно, основаны на других библиотеках шаблонов).

Есть другие действующие подходы и технологии для яваскрипта MVC и DOM Templating, например , на основе SproutCore , Backbone.js и рулю . Рекомендуется основывать приложение на одном или нескольких из них. Мы также должны избегать написания кода, подобного приведенному ниже, потому что он смешивает логику контроллера с логикой представления:

// try NOT to do that (or at least not too often...)
var painfulDiv = document.createElement('div');
painfulDiv.setAttribute('id','lastCenturyDiv');
painfulDiv.innerHTML = "There are'nt many excuses anymore to create DOM elements like this...";
document.getElementById("zoo").appendChild(painfulDiv);

 

Используйте функциональное программирование

Резюме:  функциональное программирование делает код более читабельным.

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

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

Предположим, например, что у нас есть массив пользователей, и мы хотим получить его по имени пользователя. Функциональное программирование делает это одним вкладышем:

var user = _.find(users, function(oneUser) {return oneUser.username == 'robert'});

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

var submittedAssets = _(assets).chain()
                               .filter(function (asset) {return asset.isModified})
                               .invoke(adjustStatusIfRequired)
                               .map(function (asset) { return new StaticResource(asset)})
                               .value();
  • строка 1 оборачивает объект подчеркивания вокруг исходного массива и начинает цепочку вызовов функций
  • тогда строка 2 позволяет нам указать, какое подмножество массива мы хотим обновить (в этом случае те, которые помечены как измененные)
  • строка 3 применяет функцию AdjustStatusIfRequired для каждого отфильтрованного актива
  • Строка 4 использует функцию map для вызова конструктора StaticResource для каждого полученного элемента и создает новый массив с результатом
  • наконец, .value () в строке 5 — это техническая составляющая, необходимая для прекращения подчеркивания при вызове функций

Здесь снова, использование for / loop эквивалента работает нормально, но гораздо менее чисто и читабельно.

Вывод

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

Мир или javascript третьих лиц огромен и быстро развивается. Мои примеры основаны на небольшом подмножестве того, что доступно сегодня, и, вероятно, устареют быстрее, чем принципы, которые они иллюстрируют. Важно тратить время на то, чтобы освежить нашу культуру таких библиотек, в каждый момент появляются новые интересные вещи, которые облегчают нашу работу.:-)

 

От http://svendvanderveken.wordpress.com/2012/01/04/introduction-to-clean-javascript-design/