Jaralash

Материал из support.qbpro.ru

Шаблоны проектирования JavaScript

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

Что такое шаблон проектирования?

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

И так, почему важно знать и понимать шаблоны проектирования? Шаблоны проектирования имеют три основных преимущества:

  1. Шаблоны представляют собой проверенные решения. Они обеспечивают твердые подходы к решению вопросов в разработке программного обеспечения, используя проверенные методы, которые отражают опыт и знания разработчиков, которые помогли определить их и довести до шаблона.
  2. Шаблоны могут быть легко использованы повторно: обычно отражает модель из коробки решения, которые могут быть адаптированы к нашим собственным потребностям. Эта особенность делает их весьма надежными.
  3. Шаблоны могут быть выразительным: Когда мы смотрим на шаблон в целом, то присутствует определенная структура, и готовые пути решения, с помощь которых можно представить решение сложной задачи простым способом.

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

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


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

Хотя эти шаблоны проектирования могут применяться в любом языке программирования, тем не менее многие годы они изучаются с позиций языков со строгим контролем типов и со статическими классами, таких как C++ и Java.

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

Нами будут рассмотрены следующие шаблоны проектирования:

  • Пространство имен
  • Частные свойства и методы
  • Модуль
  • Шаблон модуль выявление
  • Одиночка
  • Наблюдатель
  • Посредник
  • Прототип
  • Команда
  • Фасад
  • Фабричный метод
  • Смешанный шаблон
  • Декоратор
  • Приспособленец

Мы уже используем шаблоны в повседневной жизни

Чтобы понять, какую пользу дают шаблоны, давайте рассмотрим очень простой пример, проблему выбора DOM элементов, которую библиотека JQuery решает за нас.

Представьте себе, что у нас есть сценарий, где нужно найти на странице элементы с классом "Foo" Каким будет самый эффективный способ решения? Вот несколько различных способов, решения это проблемы:

  1. Выбрать все элементы на странице, и путем ручного перебора проверять каждый элемент, соответствует ли его класс тому, который мы ищем.
  2. Использовать современную строенную функцию браузера querySelectorAll() для выбора всех элементов с классом “Foo”.
  3. Использование встроенной функцией такой как getElementsByClassName() и получить нужную коллекцию.

Итак, какой из этих вариантов является самым быстрым? Это на самом деле вариант под номером 3, С коэффициентом в 8-10 раз. Но в реальных приложениях, 3-й вариант может и не работать, так как в версии Internet Explorer ниже 9 поддерживают его не полностью и в таком случае необходимо использовать другие способы .

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

Категории шаблонов проектирования

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

Порождающие (Creational) шаблоны проектирования

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

Некоторые из шаблонов которые попадают в эту категорию:

  • Абстрактная фабрика (Abstract Factory).
  • Прототип (Prototype).
  • Одиночка (Singleton).
  • Строитель (Builder).

Структурные (Structural) шаблоны проектирования

Это шаблоны проектирования, в котором рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.

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

Некоторые из шаблонов которые попадают в эту категорию:

  • Декоратор (Decorator).
  • Фасад (Facade).
  • Приспособленец (Flyweight).
  • Адаптер (Adapter).
  • Заместитель (Proxy).

Поведенческие (Behavioral) шаблоны проектирования

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

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

Некоторые из шаблонов которые попадают в эту категорию:

  • Итератор (Iterator).
  • Посредник (Mediator).
  • Наблюдатель (Observer).
  • Посетитель (Vistor).

Общая таблица категорий шаблонов проектирования

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


Creational (Порождающие) На основе концепции создания объекта
Class
Factory Method (Фабричный метод) Создает экземпляры нескольких производных классов на основе данных интерфейса или событий
Object
Abstract Factory (Абстрактная фабрика) Предоставляет интерфейс для создания семейств, связанных между собой, или независимых объектов, конкретные классы которых неизвестны
Builder (Строитель) Отделяет конструирование сложного объекта от его представления, позволяя использовать один и тот же процесс конструирования для создания различных представлений
Prototype (Прототип) Описывает виды создаваемых объектов с помощью прототипа и создает новые объекты путем его копирования
Singleton (Одиночка) Гарантирует, что некоторый класс может иметь только один экземпляр, и предоставляет
Structural (Структурные) Базируется на построении блоков из объектов
Class
Adapter (Адаптер) Преобразует интерфейс класса в некоторый другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного шаблона из-за несовместимости интерфейсов
Object
Adapter (Адаптер) Преобразует интерфейс класса в некоторый другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного шаблона из-за несовместимости интерфейсов
Bridge (Мост) Отделяет абстракцию от реализации, благодаря чему появляется возможность независимо изменять и то и другое
Composite (Компоновщик) Группирует объекты в древовидные структуры для представления иерархий типа “часть-целое”. Позволяет клиентам работать с единичным объектами так же, как с группами объектов
Decorator (Декоратор) Динамически возлагает на объект новые функции. Декораторы применяются для расширения имеющейся функциональности и являются гибкой альтернативой порождения подклассов
Facade (Фасад) Представляет унифицированный интерфейс к множеству интерфейсов в некоторой подсистеме. Определяет интерфейс более высокого уровня, облегчающий работу с подсистемой
Flyweight (Приспособленец) Использует разедление для эффективной поддержки большого числа мелких объектов
Proxy (Заместитель) Подменяет другой объект для контроля доступа к нему
Behavioral (Поведенческие) На основе концепции создания объекта
Class
Interpreter (Интерпретатор) Для заданного языка определяется представление его грамматики, а также интерпретатор предложений языка, использующий это представление
Template Method (Шаблонный метод) Определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. Позволяет подклассам переопределять шаги алгоритма, не меняя его общей структуры
Object
Chain of Responsibility (Цепочка обязаностей) Можно избежать жесткой зависимости отправителя запроса от его получателя, при этом запросом начинает обрабатываться один из несколько объектов. Объекты получатели связываются в цепочку, и запрос передается по цепочке, пока какой-то объект его не обработает
Command (Команда) Инкапсулирует запрос в виде объекта, позволяя тем самым параметризировать клиентов типом запроса, устанавливать очередность запросов, протоколировать их и поддерживать отмену выполнения операций
Iterator (Итератор) Дает возможность последовательно обойти все элементы составного объекта, не расскрывая его внутреннего представления
Mediator (Посредник) Определяет объект, в котором инкапсулировано знание о том, как взаимодействуют объекты из некоторого множества. Способствует уменьшению числа связей между объектами, позволяя им работать без явных ссылок друг на друга. Это, в свою очередь, дает возможность независимо изменять схему взаимодействия
Memento (Хранитель) Позволяет, не нарушая инкапсуляции, получить и сохранить во внешней памяти внутренее состояние объекта, чтобы позже объект можно было восстановить точно в таком же состоянии
Observer (Наблюдатель) Определяет между объектами зависимость типа один - ко - многим, так что при изменении состоянии одного объекта все зависящие от него получают извещение и автоматически обновляются
State (Состояние) Позволяет объекту варьировать свое поведение при изменении внутреннего состояния. При этом создается впечатление, что поменялся класс объекта
Strategy (Стратегия) Определяет семейство алгоритмов, инкапсулируя их все и позволяя подставлять один вместо другого. Можно менять алгоритм независимо от клиента, который им пользуется
Visitor (Посетитель) Представляет операцию, которую надо выполнять над элементами объекта. Позволяет определить новую операцию, не меняя классы элементов, к которым он применяется

Шаблон “Пространство имен”

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

Взгляните на следующий пример:

// ДО: 5 глобальных переменных
function Parent() {}
function Child() {}
var some_var = 1;
var module1 = {};
module1.data = {a: 1, b: 2};
va module2 = {};

Такой программный код легко переписать иным способом, создав единственный глобальный объект, назовем его MYAPP, и превратив все функции и переменные в свойства этого глобального объекта:

// ПОСЛЕ: 1 глобальная переменная 
// глобальный объект
var MYAPP = {}; 
// конструкторы 
MYAPP.Parent = function () {}; 
MYAPP.Child = function () {}; 
// переменная 
MYAPP.some_var = 1; 
// обьект-контейнер 
MYAPP.modules = {}; 
// вложенные обьекты 
MYAPP.modules.modulel = {}; 
MYAPP.modules.modulel.data = {a: 1, b: 2}; 
MYAPP.modules.module2 = {}; 

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

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

// применение функции пространства имен 
MYAPP.namespace('MYAPP.modules.module2'); 
// этот вызов эквивалентен следующей конструкции: 
//  var MYAPP = { 
//      modules: { 
//          module2: {} 
//      } 
//  }; 

Далее приводится пример реализации этой функции, в котором использован принцип неразрушения, то есть если пространство имен с заданным именем уже существует, оно не будет создано заново:

var MYAPP = MYAPP || {}; 
MYAPP.namespace = function (ns_string) { 
          var parts = ns_string.split('.'), 
          parent = MYAPP, 
          i; 
         // отбросить начальный префикс - имя глобального объекта 
        if (partsf[0] === "MYAPP") { 
        parts = parts.slice(1); 
        } 
        for (i = 0; i < parts.length; i += 1) { 
            // создать свойство, если оно отсутствует 
           if (typeof parent[parts[i]] === "undefined") { 
               parent[parts[i]] = {}; 
            } 
           parent = parent[parts[i]]; 
       } 
       return parent; 
}; 

Такая реализация делает допустимыми все следующие варианты использования функции:

// присваивать возвращаемое значение локальной переменной 
var module2 = MYAPP.namespace(‘MYAPP.modules.module2’); 
module2 === MYAPP.modules.module2; // true 
// опускать начальный префикс 'MYAPP'
MYAPP.namespace('modules.module51'); 
// создавать глубоко вложенные пространства имен 
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property' ); 

Шаблон “Частные свойства и методы”

В языке JavaScript нет специальных средств объявления частных (private), защищенных (protected) или общедоступных (public) свойств и методов, как в языке Java или в других языках. Все члены объектов в этом языке являются общедоступными:

var myobj = { 
                 myprop: 1, 
                getProp: function () { 
                         return this.myprop; 
                 } 
}; 
console.log(myobj.myprop); // 'myprop' - общедоступный член 
console.log(myobj.getPropO); // getPropO - также общедоступный член 

To же справедливо и при использовании функций-конструкторов для создания объектов - все члены являются общедоступными:

function Gadget() { 
         this.name = 'iPod'; 
         this.stretch = function () { 
                   return 'iPad'; 
        }; 
} 
var toy = new GadgetO; 
console.log(toy.name); // 'name' - общедоступный член 
console.log(toy.stretch()); // stretch() - общедоступный член 

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

function Gadget() { 
        // частный член 
         var name = ‘iPod’; 
       // общедоступная функция 
        this.getName = function () { 
               return name; 
        }; 
}
var toy = new Gadget(); 
// имя 'name' не определено, частный член 
console.log(toy.папе); // undefined 
// общедоступный метод может обратиться к частному члену 'name'
console.log(toy.getName()); // "iPod"

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

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

var myobj; // это будет объект 
(function () { 
         // частные члены 
         var name = "my, oh my"; 
         // реализация общедоступных членов 
         // обратите внимание на отсутствие инструкции 'var'
          myobj = { 
               // привилегированный метод 
              getName: function () { 
                     return name; 
               } 
         }; 
}()); 
myobj.getName(); // "my, oh my"

Та же идея положена в основу следующего примера, имеющего несколько иную реализацию:

var myobj = (function () { 
          // частные члены 
        var name = "my, oh my"; 
         // реализация общедоступных членов 
        return { 
               getName: function () { 
                       return name; 
              } 
}; 
}()); 
myobj.getName(); // "my, oh my"

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

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

Фактически эта проблема относится ко всем членам, добавляемым внутри конструкторов. Чтобы сэкономить свои усилия и память, общие для всех экземпляров свойства и методы можно добавить в свойство prototype конструктора. При таком подходе общие члены будут совместно использоваться всеми экземплярами, созданными с помощью одного и того же конструктора. Аналогичным образом можно определять частные свойства, совместно используемые всеми экземплярами. Для этого необходимо применить комбинацию из двух шаблонов: частные свойства внутри конструкторов и частные свойства в литералах объектов. Так как свойство prototype является обычным объектом, его можно определить в виде литерала.

Как это сделать, показано в следующем примере:

function Gadget() { 
         // частный член 
         var name = 'iPod'; 
         // общедоступная функция 
          this.getName = function () { 
                return name; 
         }; 
} 
Gadget.prototype = (function () { 
          // частный член 
         var browser = "Mobile Webkit"; 
         // общедоступные члены прототипа 
         return { 
               getBrowser: function () { 
                     return browser; 
              } 
        }; 
}()); 
var toy = new Gadget(); 
console.log(toy.getName()); // "собственный" привилегированный метод 
console.log(toy.getBrowser()); // привилегированный метод прототипа

Базовые Namespace паттерны JavaScript

Оригинал http://www.zencoder.pro/essential-js-namespacing

Эта статья раскрывает варианты применения паттернов среднего и сложного уровня к пространствам имён(Namespace) в JavaScript.

  • Паттерн (англ. 'pattern — образец, шаблон, система) - Смысл термина «паттерн» больше уже чем просто «образец», и варьируется в зависимости от области знаний, в которой используется. Паттерн (информатика) — эффективный способ решения характерных задач проектирования, в частности проектирования компьютерных программ.

Что такое Namespacing в JavaScript

Во многих языках программирования Namespacing является ключевой техникой позволяющей избежать путаницы объектов в глобальном пространстве имён. Более того, качественная организация Namespace позволяет сгруппировать функциональные блоки в контролируемые группы, которые могут быть в последующем однозначно идентифицированы.

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

Не смотря на то, что JavaScript не имеет встроенной поддержки Namespace, как другие языки программирования, в нём есть объекты и closeure(замыкания), которые позволяют достичь нужного эффекта.

Продвинутые паттерны организации Namespace

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

Автоматизация вложенных(nested) Namespace

Возможно, вам известно, что nested Namespace представляет собой иерархическую организацию структур. Рассмотрим, как структура вида application.utilities.drawing.canvas.2d может выглядеть на практике. Применительно к JavaScript это литерал-объект:

var application = {  
           utilities:{  
                   drawing:{  
                           canvas:{  
                                   2d:{  
                                           /*...*/  
                                   }  
                           }  
                   }  
           }  
};

Ух, это уныло.

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

Как решить эту проблему изящнее? В книге JavaScript Patterns Стоян Стефанов предлагает весьма грамотный подход для организации вложенных пространств имён при существующей глобальной переменной. Метод принимает однострочный аргумент для гнезда, парсит его и автоматически заполняет Namespace вместе с требуемыми объектами.

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

// верхний уровень namespace объявлен как литерал-объект 
var myApp = myApp || {};  
// функция для разборки строки Namespace и автоматической генерации вложенной иерархии 
function extend( ns, ns_string ) {  
   var parts = ns_string.split('.'),  
       parent = ns,  
       pl, i;  
   if (parts[0] == "myApp") {  
       parts = parts.slice(1);  
   }  
   pl = parts.length;  
   for (i = 0; i < pl; i++) {  
       //create a property if it doesnt exist  
       if (typeof parent[parts[i]] == 'undefined') {  
           parent[parts[i]] = {};  
       }  
       parent = parent[parts[i]];  
   }  
   return parent;  
}  

// пример применения: расширение myApp с глубокой вложенностью namespace  
var mod = extend(myApp, 'myApp.modules.module2');  
// на выходе: корректно вложенная в объект иерархия  
console.log(mod);  

// мы можем проверить это используя экземпляр объекта за пределами myApp namesapce, 
// как клон, включающий в себя расширения  
console.log(mod == myApp.modules.module2); //true  

// следом, более простая демонстрация объявления с использованием extend    
extend(myApp, 'moduleA.moduleB.moduleC.moduleD');  
extend(myApp, 'longer.version.looks.like.this');  
console.log(myApp);  

Вот что мы увидим в web-инспекторе:

Ns1.png

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

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


Шаблон зависимых объявлений

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

// общий подход к доступу пространств имён

myApp.utilities.math.fibonacci(25);  
myApp.utilities.math.sin(56);  
myApp.utilities.drawing.plot(98,50,60);  

// объявление локальных(кешируемых) ссылок

Var utils = myApp.utilities,  
maths = utils.math,  
drawing = utils.drawing;  

// более короткий путь к namespace

maths.fibonacci(25);  
maths.sin(56);  
drawing.plot(98, 50,60);  

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

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

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

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

Глубинные расширения объектов

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

Это как раз то, что стало легко выполнимо с помощью JavaScript фреймворков(например, метод jQuery $.extend). Тем не менее, если вам нужно повторить это в стиле «vanila js», следующие инструкции могут быть полезны.

// extend.js // written by andrew dupont, optimized by addy osmani

function extend(destination, source) {  
   var toString = Object.prototype.toString,  
       objTest = toString.call({});  
   for (var property in source) {  
       if (source[property] && objTest == toString.call(source[property])) {  
           destination[property] = destination[property] || {};  
           extend(destination[property], source[property]);  
       } else {  
           destination[property] = source[property];  
       }  
   }  
   return destination;  
};  
console.group("objExtend namespacing tests");  

// определение top-level namespace

var myNS = myNS || {};  

// 1. расширение namespace объектом 'utils'

extend(myNS, {  
       utils:{  
       }  
});  
console.log('test 1', myNS);  

// myNS.utils теперь существует // 2. расширение объекта несколькими значениями (namespace.hello.world.wave)

extend(myNS, {  
               hello:{  
                       world:{  
                               wave:{  
                                   test: function(){  
                                       /*...*/  
                                   }  
                               }  
                       }  
               }  
});  

// проверка ссылок на работоспособность

myNS.hello.test1 = 'this is a test';  
myNS.hello.world.test2 = 'this is another test';  
console.log('test 2', myNS);  

// 3. а что если myNS уже имеет namespace (например, 'library')?

myNS.library = {  
       foo:function(){}  
};  
extend(myNS, {  
       library:{  
               bar:function(){  
                   /*...*/  
               }  
       }  
});  

// проверим работает ли extend так как ожидалось myNS сейчас содержит library.foo и library.bar

console.log('test 3', myNS);  

// 4. а что, если захочется иметь более простой доступ к отдельному пространству имён без необходимости каждый раз указывать полный путь

var shorterAccess1 = myNS.hello.world;  
shorterAccess1.test3 = "hello again";  
console.log('test 4', myNS);  
 

// победа, myApp.hello.world.test3 теперь доступно через 'hello again'

console.groupEnd();  

Если же ваше приложение использует jQuery, то расширения namespace объекта можно достичь с помощью $.extend.

// top-level namespace

var myApp = myApp || {};  

// явное расширение namespace вглубь

myApp.library = {  
   foo:function(){ /*..*/}  
};  

// расширим наш namespace другим объектом, но // чтобы было интереснее сделаем вложенное пространство имён функцией // $.extend(deep, target, object1, object2)

$.extend(true, myApp, {  
   library:{  
       bar:function(){  
           /*..*/  
       }  
   }  
});  
console.log('test', myApp);

// myApp теперь содержит методы library.foo() и library.bar() // ни одно из namespace не подверглось корреляции, на что мы и рассчитывали.

Для большей наглядности посмотрите другие примеры $.extend и поэкспериментируйте с ними.

Основы построения пространств имён

Пространства имён встречаются в любом мало-мальски серьёзном приложении. Если вы работаете с разрозненными фрагментами кода, то делаете максимум возможного, чтобы убедиться, что пространства имён организованы правильно. Это защищает данные вашего приложения от перспективы быть затёртым другим приложением. В этом разделе мы будем рассматривать следующие шаблоны:

  1. Одиночные глобальные переменные (Single global variables)
  2. Объектно-буквенное обозначение (Object literal или литерал-объект)
  3. Глубинное именование (Nested namespacing)
  4. Объявление самовызывающейся функции (Immediately-invoked Function Expressions)
  5. Внедрение в Namespace (Namespace injection)

Одиночные глобальные переменные

Одним из популярных шаблонов для организации Namespace в JavaScript является выбор одной глобальной переменной в качестве основного объекта для ссылки. Каркас такого паттерна возвращает объект с функцией и свойствами:

var myApplication =  (function(){  
       function(){  
           /*...*/  
       },  
       return{  
           /*...*/  
       }  
})();  

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

Возможный вариант решения упоминался Петер Мичаукс. Решение заключается в довольно простой идее использовать префиксы для имён. Сначала вы именуете своё приложение, а затем объекты, методы, функции или свойства, например:

var myApplication_propertyA = {};  
var myApplication_propertyB = {};  
funcion myApplication_myMethod(){ /*..*/ }  

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

Если вас интересует мнение Петера в отношении этого вопроса, можете прочитать статью на эту тему.

Object literal notation

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

var myApplication = {  
   getInfo:function(){ /**/ },
   // мы также можем организовать литерал-объект для надёжности  
   models : {},  
   views : {  
       pages : {}  
   },  
   collections : {}  
};

Естественно, мы можем определить какие-то свойства для Namespace напрямую.

myApplication.foo = function(){  
   return "bar";  
}  
myApplication.utils = {  
   toString:function(){  
       /*..*/  
   },  
   export: function(){  
       /*..*/  
   }  
} 

Шаблон «Object literal notation» имеет достоинства не загрязнять глобальное пространство имён, помогать в логической организации кода и параметров. Это очень полезно, если вы хотите создавать легко читаемые структуры кода, которые могут быть в последующем расширяться вглубь. В отличие от обычных глобальных переменных объектно-буквенный namespace также учитывают тесты на существование переменной, поэтому шансы на путаницу значительно сокращаются.

Следующий фрагмент кода демонстрирует способы, которыми вы можете проверить наличие переменной(namespace) в глобальном пространстве, перед тем, как определить её. Вы будете чаще встречать вариант 1, однако, варианты 3 и 5 являются более тщательными, в то время, как вариант 4 считается лучшей практикой.

// Плохо: // этот вариант не предусматривает проверки глобального namespace на наличие 'myApplication'.

 var myApplication = {};  

/* Следующие методы предназначены для проверки на существование переменной.

Если переменная существует, используется экземпляр. В противном случае создаётся новый литерал-объект для myApplication

  • /

// Option 1:

 var myApplication = myApplication || {}; 

// Option 2:

 if(!MyApplication) MyApplication = {}; 

// Option 3:

 var myApplication = myApplication = myApplication || {} 

// Option 4:

 myApplication || (myApplication = {}); 

// Option 5:

 var myApplication = myApplication === undefined ? {} : myApplication; 

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

var namespace = (function () {  
   // объявление в локальной области  
   var privateMethod1 = function () { /* ... */ }  
   var privateMethod2 = function () { /* ... */ }  
   var privateProperty1 = 'foobar';  
   return {

       // здесь мы возвращаем литерал-объект, который может иметь
       // столько уровней в глубину, сколько вы пожелаете.
       // но, как упоминалось выше, этот вариант лучше всего подходит
       // небольших приложений с ограниченной областью видимости  
       publicMethod1: privateMethod1,  

       //вложенные пространства имён с публичными свойствами  
       properties:{  
           publicProperty1: privateProperty1  
       },  

       //ещё одно адресное пространство  
       utils:{  
           publicMethod2: privateMethod2  
       }  
       ...  
   }  
})();  

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

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

var myConfig = {  
   language: 'english',  
   defaults: {  
       enableGeolocation: true,  
       enableSharing: false,  
       maxPhotos: 20  
   },  
   theme: {  
       skin: 'a',  
       toolbars: {  
           index: 'ui-navigation-toolbar',  
           pages: 'ui-custom-toolbar'  
       }  
   }  
}  

Стоит обратить внимание на то, что существуют незначительные отличия в синтаксисе при определении литерал-объекта и стандартного набора данных в формате JSON. Если вы по какой либо причине склоняетесь к JSON для организации параметров(например, для более удобного обмена с back-end частью приложения), не стесняйтесь. Более подробно о литерал-объекте можно узнать в статье Реббека Мерфи.

Вложенные именования

«Nested namespace» является расширением шаблона литерал-объекта. Это ещё одна распространённая модель обеспечивающая снижение рисков путаницы также и в локальной области видимости.

Возможно вы уже где-то видели это?

YAHOO.util.Dom.getElementsByClassName('test');

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

var myApp =  myApp || {}; 

// проверки на существование в т.ч. при определении потомков

myApp.routers = myApp.routers || {};  
myApp.model = myApp.model || {};  
myApp.model.special = myApp.model.special || {};  

// вложенное пространство имён может быть таким сложным, как потребуется // myApp.utilities.charting.html5.plotGraph(/*..*/); // myApp.modules.financePlanner.getSummary(); // myApp.services.social.facebook.realtimeStream.getLatest();

Для определения новых вложенных namespace вы можете обратиться к свойствам по индексу.

myApp["routers"] = myApp["routers"] || {};  
myApp["models"] = myApp["models"] || {};  
myApp["controllers"] = myApp["controllers"] || {};  

Оба варианта вполне читабельны, организованы и позволяют относительно безопасно спроектировать адресное пространство, подобно тому, как это сделано в других языках. Единственный момент, который нужно учесть: это заставит логику движка JavaScript вашего браузера сначала рассчитать объект myApp, а затем «копать вглубь» вплоть до функции, которую вы желаете использовать.

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

Объявление самовызывающейся функции (IIFE)

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

Простейшая реализация IIFE может выглядеть следующим образом:

// immediately-invoked function expression (анонимная)

(function(){ /*...*/})();  

// immediately-invoked function expression (именованная)

(function foobar(){ /*..*/}());  

// техника для реализации самовызова функции из внутреннего контекста

function foobar(){ foobar(); }  

Чуть более развёрнутый вариант будет таким:

var namespace = namespace || {};  

// здесь объект namespace представлен, как параметр функции

// с объявлением публичных методов и их свойств

(function( o ){  
   o.foo = "foo";  
   o.bar = function(){  
       return "bar";  
   };  
})(namespace);  
console.log(namespace);

Для большей наглядности этот пример может быть развёрнут до уровня видимости различных уровней приватности(public/private функций и переменных) и удобного обозрения namespace-расширений. Взглянем на следующий пример:

// 1. namespace может быть изменено локально и не может быть перезаписано вне своего контекста

// 2.значение undefined гарантирует, что параметры действительно не определены.

// это обезопасит приложение от подмены входных данных(mutable pre-ES5).

;(function ( namespace, undefined ) {  
   // private properties  
   var foo = "foo",  
       bar = "bar";  

   // public methods and properties  
   namespace.foobar = "foobar";  
   namespace.sayHello = function () {  
       speak("hello world");  
   };  

   // private method  
   function speak(msg) {  
       console.log("You said: " + msg);  
   };  

   // проверим есть ли 'namespace' в глобальном контексте; если нет, то объявим window.namespace литерал-объектом.  
}(window.namespace = window.namespace || {});  

// проверим наши свойства

// public

console.log(namespace.foobar); // foobar  
namescpace.sayHello(); // hello world  

// попробуем определить новые

namespace.foobar2 = "foobar";  
console.log(namespace.foobar2); 

Расширения(или extensions) несомненно являются ключом к любой масштабируемой модели namespace и IIFE может быть весьма эффективно использованы для этих целей. В следующем примере, 'namespace' вновь передаётся в качестве аргумента в анонимную функцию и далее расширяется.

// давайте расширим namespace новой функциональностью

(function( namespace, undefined ){  
   // public method  
   namespace.sayGoodbye = function(){  
       console.log(namespace.foo);  
       console.log(namespace.bar);  
       speak('goodbye');  
   }  
}( window.namespace = window.namespace || {}); 

// в результате

namespace.sayGoodbye(); //goodbye

Если вам интересно чуточку больше, то вы можете почитать пару заметок о анонимных функциях Бена и о namespace-паттернах в C# Элии Мэнора.

Внедрение в namespace (Namespace injection)

«Namespace injection» является ещё одним из вариантов IIFE, когда мы вводим методы или свойства в определённое пространство имён из функции обёртки, используя это в качестве мини прокси-сервера. Преимущество данного паттерна заключается в лёгкости применения определённого поведения к нескольким объектам или namespace. Также это может быть полезно при определении базовых методов, которые могут быть в последующем использованы(getters и setters).

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

Следующий пример показывает, как мы можем использовать Namespace injection для заполнения параллельно двух namespace. Одно из них utils, а второе его часть подключаемая динамически в пространстве имён tools.

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


// объявим namespace, котрый используем позже

var ns = ns || {}, ns2 = ns2 || {};  

// формирователь

var creator = function(val){  
   var val = val || 0;  
   this.next = function(){  
       return val++  
   };  
   this.reset = function(){  
       val = 0;  
   }  
}  
creator.call(ns);  

// ns.next, ns.reset теперь созадны

creator.call(ns2, 5000);  

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

В заключение

Из всех представленных выше шаблонов организации namespace, которые мне доводилось использовать в большинстве сложных приложений, лидируют паттерны с глубинным расширением и литрал-объект.

IIFEs и «single global variable» могут неплохо работать как для построения малых и средних приложений, так и для больших объемов кода с глубокой вложенностью. Данная модель достигает своих целей читабельности и масшабируемости достаточно хорошо.

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

Javascript. Шаблоны проектирования. Повторное использование программного кода.

источник

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

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

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

Нами будет рассмотрены различные способы реализации наследования в JavaScript и такие понятия, как классическое наследование и не классическое. Под классическим наследованием подразумевается ситуация, что бы объекты создаваемые функцией-конструктором Child(), приобретали свойства, присущие другому конструктору Parent().

Приведем пример реализации конструкторов Parent() и Child():

function Parent(name) {
    this.name = name || "Adam";
}


Parent.prototype.say = function () {
    return this.name;
};
 
function Child(name) {}
 

inherit(Child, Parent);


Здесь у нас имеются родительский и дочерний конструкторы, метод say(), добавленый в прототип родительского конструктора, и вызов функции inherit(), которая устанавливает зависимость наследования.

Классический шаблон №1: Шаблон по умолчанию

Наиболее часто используемый и простой в реализации шаблон, суть которого заключается в присвоении свойств и методов объекта Parent(), к объекту Child().

Делается єто следующим образом:


function inherit(C, P) {

    C.prototype = new P();

}

Когда будет создаваться объект спомощью выражения new Child(), этот объект будет наследовать функциональность экземпляра Parent(), через протоип.


var child = new Child();

child.say(); // "Adam"

Таким образом, экземпляр объекта child, будет иметь свойства и методы добавленные через прототип объекта Parent().

Достоинства и недостатки

Достоинствами этого шаблона есть то, что объект Child() получает все свойства и методы объекта Parent(), и простота реализации.

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

Пример:

var button = document.getElementById("button");

button.addEventListener("click", function () {

    test();
}, false);

function inherit(C, P) {
    C.prototype = new P();
}

/**
 * Phone
 */

function Phone(name, size) {
    this.name = name || "Phone";
    this.size = size || {
        w: 50,
        h: 50
    };
}
 

Phone.prototype.getName = function () {
    alert(this.name);
};

Phone.prototype.getSize = function () {
    var size = this.size;
    alert("width: " +size.w + "; height: " + size.h);

};

/**
 * Nokia
 */

function Nokia(name, size) {
    if (name) {
        this.name = name;
    }
    if (size) {
        this.size = size;
    }
};
 
function test() {
    inherit(Nokia, Phone);
    var nokia_1 = new Nokia(),
        nokia_2 = new Nokia("Nokia 6600", {w: 100, h: 200});
    nokia_1.getName();
    nokia_1.getSize();
    nokia_2.getName();
    nokia_2.getSize();
  
}

В действии: http://jsfiddle.net/valsie/G8uWG/4/

Как видим из примера, объект Child() унаследовал методы и свойства объекта Parent().

Классический шаблон №2: Заимствование конструктора

В этом шаблоне исключается недостаток классического шаблона №1, пердача параметров через дочерний объект к родительскому. В этом шаблоне выполняется связывание дочернего объекта со ссылкой this.

function Child(a, b, c, d) {
    Parent.apply(this, arguments);
}

При таком подходе будут наследоваться только свойства добавленные внутри конструктора, а свойства прототипа - не наследуются.

Рассмотрим следующий пример:

var button = document.getElementById("button");

button.addEventListener("click", function () {
    test();
}, false);

/**
 * Phone
 */

function Phone(name, size) {
    this.name = name || "Phone";
    this.size = size || {
        w: 50,
        h: 50
    };
  
    this.showNameAndSize = function () {
        var size = this.size;
        alert(name + " - width: " + size.w + ", height: " + size.h);
    };
}

Phone.prototype.getName = function () {
    alert(this.name);
};

Phone.prototype.getSize = function () {
    var size = this.size;
    alert("width: " +size.w + "; height: " + size.h);
};

/**
 * Nokia
 */

function Nokia(name, size) {
    Phone.apply(this, arguments);
};

function test() {
    var nokia_1 = new Nokia(),
        nokia_2 = new Nokia("Nokia 6600", {w: 100, h: 200});
 
    if (nokia_1.hasOwnProperty("getName")) {
        nokia_1.getName();
    } else {
        console.log("getName is undefined");
    }

    if (nokia_1.hasOwnProperty("getSize")) {
        nokia_1.getSize();
    } else {
        console.log("getSize is undefined");
    }
     
    if (nokia_1.hasOwnProperty("showNameAndSize")) {
        nokia_1.showNameAndSize();
    } else {
        console.log("showNameAndSize is undefined");
    }

    if (nokia_2.hasOwnProperty("getName")) {
        nokia_2.getName();
    } else {
        console.log("getName is undefined");
    }

    if (nokia_2.hasOwnProperty("getSize")) {
        nokia_2.getSize();
    } else {
        console.log("getSize is undefined");
    }
   
    if (nokia_2.hasOwnProperty("showNameAndSize")) {
        nokia_2.showNameAndSize();
    } else {
        console.log("showNameAndSize is undefined");
    }
}

В действии: http://jsfiddle.net/valsie/nnhQ2/1/

Достоинства и недостатки

Недостаток этого шаблона заключается в том, что он не обеспечивает наследования свойств прототипа.

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

Классический шаблон №3: Заимствование и установка прототипа

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


function Child(a, b, c, d) {

   Parent.apply(this, arguments);

}

Child.prototype = new Parent();

Преимущество - дочерний объект получается копии собственных членов родителя и ссылку на функции прототипа.

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

Классический шаблон №4: Совместное использование прототипа

function inherit(C, P) {

   C.prototype = P.prototype;

}

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

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

Классический шаблон №5: Временный конструктор

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


function inherit(C, P) { 2

   var F = function () {};

3

   F.prototype = P.prototype;

4

   C.prototype = new F();

5 } Важно помнить, что при таком способе наследования, свойства и методы самого объекта родителя не наследуются, наследуются только свойства и методы прототипа.

Для расширения возможностей фукнции inherit, можно добавить сохранение ссылка на класс родителя.


function inherit(C, P) { 2

   var F = function () {};

3

   F.prototype = P.prototype;

4

   C.prototype = new F();

5

   C.superClass = P.prototype;

6 } Последнее что можно сделать, что бы эта функция была приближенная к идеалу, это исключить постоянное создание пустой функции F при обращении к функции inherit.


var inherit = (function () { 2

   var F = function () {};

3

   return function (C, P) {

4

       F.prototype = P.prototype;

5

       C.prototype = new F();

6

       C.superClass = P.prototype;

7

   };

8 }()); Пример:


var button = document.getElementById("button"); 02

03 button.addEventListener("click", function () { 04

   test();

05 }, false); 06

07

08 var inherit = (function () { 09

   var F = function () {};

10

   return function (C, P) {

11

       F.prototype = P.prototype;

12

       C.prototype = new F();

13

       C.superClass = P.prototype;

14

   };

15 }()); 16

17

18 /** 19

* Phone

20

*/

21 function Phone() {} 22

23 Phone.prototype = { 24

25

   init: function (name, size) {

26

27

       this.name = name || "Phone";

28

       this.size = size || {

29

           w: 50,

30

           h: 50

31

       };

32

   },

33

34

   getName: function () {

35

       alert(this.name);

36

   },

37

38

   getSize: function () {

39

       var size = this.size;

40

       alert("width: " +size.w + "; height: " + size.h);

41

   }

42 }; 43

44 /** 45

* Nokia

46

*/

47 function Nokia() { 48

   this.name = null;

49

   this.size = null;

50 }; 51

52 function test() { 53

54

   inherit(Nokia, Phone);

55

56

   var nokia_1 = new Nokia(),

57

       nokia_2 = new Nokia();

58

59

   nokia_1.init();

60

   nokia_2.init("Nokia 6600", {w: 100, h: 200});

61

62

   nokia_1.getName();

63

   nokia_1.getSize();

64

   nokia_2.getName();

65

   nokia_2.getSize();

66 } В действии: http://jsfiddle.net/valsie/LVH5F/

Шаблон имитации класса

Многие JavaScript библиотеки пытаются эмитировать классы, но все реализации имеют схожие черты:

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

Javascript 1 var Phone = jClass(null, { 2

   init: function (variable) {

3

       console.log("constructor");

4

       this.name = variable;

5

   },

6

   getName: function () {

7

       return this.name;

8

   }

9 }); Эта функция выполняет объявление класса и принимает два параметра, первый - класс родитель, второй - свойства и методы создаваемого класса. В данном примере, в качестве функции конструктора, была выбрана функция init.

Первый параметр функции равен null, это говорит о том, что не выполняется никакого наследования, класс будет обладать только собственными свойствами и методами.

Расширим этот класс и создадим новый:

var Nokia = jClass(Phone, { 2

   init: function (variable) {

3

       console.log("Nokia constructor");

4

   },

5

   getName: function () {

6

       var name = Nokia.superClass.getName.call(this);

7

       return "I am " + name;

8

   }

9 }); Здесь выполняется наследование класса Phone, и создание нового Nokia. Как видим, в новом классе объявляются такие функции как и в родительском, тем самым получается переопределение функций родителя, но так как сохраняется ссылка на суперкласс, мы можем обращаться к функциям родительского класса.

Перейдем к реализации функции jClass:

Javascript 01 var jClass = function (Parent, properties) { 02

   var Child, F, i;

03

04

   // 1. Новый конструктор

05

   Child = function () {

06

       if (Child.superClass && child.superClass.hasOwnProperty("init")) {

07

           Child.superClass.init.apply(this, arguments);

08

       }

09

10

       if (Child.prototype.hasOwnProperty("init")) {

11

           Child.prototype.init.apply(this, arguments);

12

       }

13

   }

14

15

   // 2. Наследование

16

17

   Parent = Parent || Object;

18

   F = function () {};

19

   F.prototype = Parent.prototype;

20

   Child.prototype = new F();

21

   Child.superClass = Parent.prototype;

22

   Child.prototype.constructor = Child;

23

24

   // 3. Добавить реализацию методов

25

26

   for (i in properties) {

27

       if (properties.hasOwnProperty(i)) {

28

           Child.prototype[i] = properties[i];

29

       }

30

   }

31

32

   return Child;

33 }; В действии: http://jsfiddle.net/valsie/pK58m/6/

Наследование через прототип

Этот шаблон не имеет никакого отношения к классами, здесь одни объекты наследуют другие.

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

Javascript 1 // объект, который наследуется 2 var parent = { 3

   name: "Papa"

4 }; 5 // новый объект 6 var child = object(parent); 7 // проверка 8 alert(child.name); // "Papa" Реализация функции object имеет следующий вид:

Javascript 1 function object(o) { 2

   function F() {}

3

   F.prototype = o;

4

   return new F();

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

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

Однако нужно учитываться, что в последнем случае унаследованы будут не только свойства прототипа конструктора, но и “собственные” свойства объекта:

Javascript 01 // родительский конструктор 02 function PersonO { 03

   // "собственное" свойство 

04

   this.name = "Adam"; 

05 } 06 // свойство, добавляемое в прототип 07 Person.prototype.getName = function () { 08

   return this.name; 

09 }; 10 // создать новый объект типа Person 11 var papa = new PersonO; 12 // наследник 13 var kid = object(papa); 14 // убедиться, что было унаследовано не только 15 // свойство прототипа, но и собственное свойство 16 kid.getName(); // "Adam" Дополнения в стандарте ECMAScript5

В стандарте ECMAScript 5 шаблон наследования черзе протип стал официальной частью языка. Этот шаблон реализован в виде метода Object.create(). Другими словами, вам не потребуется создавать собственную функцию, похожую на object(); она уже будет встроена в язык: var child = Object.create(parent);

Метод Object.create() принимает дополнительный параметр - объект. Свойства этого объекта будут добавлены во вновь созданный дочерний объект как собственные свойства. Это позволяет создавать дочерние объекты и определять отноешния наследования единственным вызовом метода.

Например:

Javascript 1 var child = Object.create(parent, { 2

   age : {

3

       value: 2

4

   }

5 }); 6

7 child.hasOwnPorperty("age"); // true Наследование через прототип в действии: http://jsfiddle.net/valsie/ZrLA7/

Наследование копированием свойств

Рассмотрим другой способ наследования - наследование копированием свойств. В этом шаблоне один объект получает доступ к функциональности другого объекта за счет простого копирования свойств. Ниже пример реализации:

Javascript 01 function extend(parent, child) { 02

   var i; 

03

   child - child || {}; 

04

   for (i in parent) { 

05

       if (parent.hasOwnProperty(i)) { 

06

           child[i] = parent[i]; 

07

       } 

08

   }

09

   return child; 

10 } В этом примере выполняется обход и копирование членов родительского объекта. В этой реализации копирования выполняется так называемое “поверхностное копирование” свойств. В таком случае, такие свойства как массивы и объекты будут передаваться в новые объекты по ссылке, и изменение в них, будет влечь за собой изменения в родительских элементах.

В случае, если необходимо выполнять полное копирование, с учетом массивов и объектов, функция extend должна выглядеть следующим образом:


function extendDeep(parent, child) { 02

   var i, 

03

         toStr = Object.prototype.toString, 

04

         astr = "[object Array]"; 

05

06

   child = child || {}; 

07

   for (i in parent) { 

08

       if (parent.hasOwnProperty(i)) { 

09

           if (typeof parent[i] === "object") { 

10

               child[i] = (toStr.call(parent[i]) === astr) ? [] : {}; 

11

               extendDeep(parent[i], child[i]); 

12

           } else { 

13

               child[i] = parent[i]; 

14

           } 

15

       }

16

   } 

17

   return child; 

18 } Теперь, с помощью этой функции можно выполнить полное копирование свойств объекта.