Статьи

Анатомия шаблона проектирования JavaScript

Это продолжение поста « Мой любимый шаблон разработки JavaScript» , в котором мы надеемся ответить на несколько вопросов, которые он поднял, и более подробно обсудить некоторые его особенности.

Те из вас, кто сравнивал шаблон, который я описал, с шаблоном модуля JavaScript, очень точны в этом, так как он выглядит очень похожим видом конструкции. А в паттерне « Откровение модуля» Кристиана Хайльмана добавлена ​​изящная дополнительная функция, обеспечивающая большую гибкость при отображении методов как общедоступных. (Спасибо за ссылки!)

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

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

Закрытые крышки

Заключение сценариев в одном корпусе значительно улучшает инкапсуляцию .

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

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

Имя или Аноним? Публичный или частный?

Анонимные закрытия недоступны извне. Это выгода? Зачем заключать сценарии в анонимные замыкания, если это просто означает, что другие сценарии не могут получить к ним доступ?

Именно поэтому! Так что другие скрипты не могут получить к ним доступ. Важным вариантом использования здесь является разработка сторонних инструментов, которые предоставляют внешнюю функциональность, но не желают раскрывать их внутреннюю механику. Некоторые сценарии просто запускают фоновые задачи и не требуют никакого ввода вообще; так что если это так, то нет смысла предоставлять какие-либо входные данные — весь сценарий можно обернуть в анонимную оболочку, и тогда вероятность глобального конфликта равна нулю.

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

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

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

Этот или тот?

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

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

 function MyScript(){} (function() { var THIS = this; function defined(x) { alert(this); //points to defined() alert(THIS); //points to MyScript() } }).apply(MyScript); 

Это может быть названо как угодно Некоторые люди называют это "that" или "self" ; Я даже пытался использовать неанглийские слова, такие как "la" или "das." Но в конце концов я остановился на "THIS" в верхнем регистре, потому что во многих языках существует давняя конвенция объявлять константы в верхнем регистре, и это, казалось, отвечало всем требованиям.

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

 var OLDER_WEBKIT = /applewebkit/([0-4]|[5][0-2])/i.test(navigator.userAgent), KONQUEROR = navigator.vendor == 'KDE'; 

В идеале мы должны использовать const а не var чтобы объявить их, поскольку истинные константы используют меньше памяти, но это не поддерживается в Internet Explorer.

Привилегированные ценности

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

 var options = { x : 123, y : 'abc' }; this.define = function(key, value) { if(defined(options[key])) { options[key] = value; } }; 

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

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

Миниатюра кредит: суперкимбо