Статьи

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

В этой статье мы рассмотрим процесс использования JavaScript с точки зрения MVC для управления DOM. Более конкретно, мы будем проектировать наши объекты JavaScript, их свойства и методы и их экземпляры параллельно предполагаемому поведению наших представлений (что видит пользователь).


На любом этапе разработки веб-страницы мы используем язык, который естественным образом поощряет либо разработку на основе классов, либо разработку на основе объектов. В строго типизированных языках, таких как Java и C #, мы обычно пишем наши представления в классах — давая им состояние, область действия и контекст. Когда мы работаем с такими языками, как PHP или более новыми механизмами представления, такими как Razor для ASP.NET, наши представления могут просто быть разметкой (HTML / CSS), смешанной с шаблонизацией. Однако это не означает, что мы должны изменить наше восприятие того, как представление ведет себя как собственная сущность с состоянием.

В пределах Views мы в основном работаем с HTML, который состоит из вложенных элементов; эти элементы имеют атрибуты, которые описывают их семантическое назначение или то, как они выглядят при визуализации. Эти элементы затем имеют дочерние или родительские элементы, которые наследуют / обеспечивают каскадирование (посредством CSS) и блочные / встроенные поведения. Эти элементы можно естественно рассматривать с точки зрения ООП (объектно-ориентированного программирования). Рассмотрим, например, следующую разметку:

1
2
3
4
5
div.container {
    border: 1px solid #333;
    padding: 5px;
    color: red;
}
1
2
3
<div class=»container»>
    <h2>About Our Company</h2>
</div>
Our header inherited a red border through CSS cascading

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

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

«Когда вы представляете свои представления и частичные представления как объекты, это значительно упрощает написание кода на стороне клиента».


Многие разработчики склонны писать JavaScript с процедурной или функциональной точки зрения и часто пренебрегают естественными тенденциями, предлагаемыми в подходах к разработке на основе представлений и параллельной реализации (создание нового экземпляра представления по мере создания нового экземпляра JavaScript). объект, соответствующий этому виду) при работе в MVC Frameworks. Часто бывает, что я сталкиваюсь с файлами JavaScript, которые представляют собой только один метод за другим. Хотя такое поведение работает и является распространенным, оно не очень эффективно для обслуживания кода, отладки или расширения текущего или будущего кода, когда вы интенсивно работаете с представлениями.

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

  • Каждое представление, отображаемое из действия на контроллере, должно иметь свой собственный объект JavaScript.
  • Каждое частичное представление, которое загружается внутри представления, должно иметь свой собственный объект JavaScript.
  • Назовите ваши объекты так же, как ваши представления (или частичные представления). Это будет иметь больше смысла для вас и всех остальных, кто касается вашего кода.
  • Используйте регистр Pascal для всех объектов (например, About, Sidebar и т. Д.). Ваши представления уже должны, так почему бы не сделать то же самое для ваших объектов JavaScript?
  • Все константы этих объектов должны храниться в конструкторе. Это означает, что если ваше представление имеет свойства, которые будут использоваться в нескольких методах, все эти методы могут получить доступ к этим свойствам.
  • Все методы, которые будут вызываться в представлении (или частичном представлении), должны быть связаны с прототипом объекта, который соответствует этому представлению.
  • Все привязки событий для представления (или частичного представления) должны содержаться в собственном методе привязки событий, который размещен в прототипе.

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

Our MVC Layout viewed with styles and scripts

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


В этой статье мы рассмотрим структуру страницы «О нас» на сайте MVC. Для начала мы создадим структуру, как показано выше на предыдущей диаграмме. Оттуда мы создадим объект About и начнем добавлять методы к прототипу. Сначала рассмотрим следующий визуальный макет:

Sample About Us Page Layout

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

Для просмотра страниц я обычно называю свои объекты JavaScript по имени представления. Вот пример моего объекта AboutView:

1
2
3
4
5
6
7
// View Filename: AboutView.cs (.NET MVC 1.0), About.cshtml (.NET MVC 3.0), or AboutView.php (PHP)
 
var About = function(pageTitle) {
    this.pageTitle = pageTitle;
    // binding events as soon as the object is instantiated
    this.bindEvents();
};

В приведенном выше примере мы создали объект JavaScript в формате функции, предоставив ему возможность служить конструктором объекта для всех методов, вызываемых для представления about. Выбирая этот формат, мы можем создать новый экземпляр этого , как мы делаем с нашим представлением на стороне сервера (говоря new AboutView(); ). Отсюда мы можем назначить свойства и методы этому объекту. Чтобы назначить методы этому объекту, нам понадобится доступ к прототипу объекта.


Разработчикам часто мешает неуловимость (и неоднозначность) объектного прототипа JavaScript.

Разработчикам часто мешает неуловимость (и неоднозначность) объектного прототипа JavaScript. Для многих это может быть сложно использовать и понимать и добавляет другое измерение в кодирование. Поскольку JavaScript становится все более ориентированным на события благодаря концепциям HTML5, AJAX и Web 2.0, JavaScript имеет тенденцию естественным образом склоняться к процедурной разработке, которую легко разрабатывать, но трудно поддерживать, масштабировать и копировать.

Думайте о слове Prototype как о неправильном. Когда я думаю о Prototype , я имею в виду «черновик» или основу для наследования, но это не совсем так.

«На самом деле, лучшая перспектива для Prototype — это указатель объекта в памяти».

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

Вот пример создания методов на прототипе объекта About :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
var About = function(pageTitle) {
    this.pageTitle = pageTitle;
    // binding events as soon as the object is instantiated
    this.bindEvents();
};
     
var About.prototype.bindEvents = function() {
     // Current context: ‘this’ is the About object
     // Place all your event bindings in one place and call them out
     // in their own methods as needed.
     $(‘ul.menu’).on(‘click’, ‘li.search’, $.proxy(this.toggleSearch, this));
};
 
var About.prototype.toggleSearch = function(e) {
      //Toggle the search feature on the page
};

Как вы можете видеть выше, мы содержали свойства объекта About внутри конструктора, создали единую точку привязки для событий привязки (в этом случае мы используем jQuery для создания привязок событий, но вы можете использовать любую платформу или Сам JavaScript) и поместили метод toggleSearch в прототип объекта About, чтобы включить этот метод в этот объект. Мы также вызвали метод bindEvents() в объекте, чтобы он вызывался при bindEvents() экземпляра.

Теперь рассмотрим следующий код для частичного вида боковой панели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
var pSidebar = function(pageTitle) {
    this.pageTitle = pageTitle;
     
    // call the bindEvents method on instantiation of the pSidebar object.
    // this will bind the events to the object
    this.bindEvents();
};
 
var pSidebar.prototype.bindEvents = function() {
     //current context: ‘this’ is the Sidebar object
     $(‘ul.menu’).on(‘click’, ‘li.has-submenu’, $.proxy(this.toggleSubMenu, this));
     $(‘input#search’).on(‘click’, $.proxy(this.openSearch, this));
};
 
var pSidebar.prototype.toggleSubMenu = function(e) {
    // toggle the submenus
    // current context: ‘this’ is the pSidebar obj
};

ПРИМЕЧАНИЕ: я назвал объект pSidebar потому что это частичное представление , а не полное представление. Я предпочитаю различать два, но проясняю.

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


Как только вы создали свои объекты, вызывать их просто. Вам больше не нужно зависеть от вашей платформы для запуска событий, когда ваш документ загружен или готов. Теперь вы можете просто создать экземпляр своего объекта, и его события будут связаны и выполнены по мере необходимости. Итак, давайте создадим экземпляр нашего объекта About :

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

1
2
3
4
<script src=»/path/to/scripts/views/about.js»></script>
<script>
    new About(«About Us»);
</script>

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
var About = function(pageTitle) {
    this.pageTitle = pageTitle;
     
    //assigning a new instance of the Sidebar Partial View to be referenced later
    this.sidebar = new pSidebar(pageTitle);
     
    //NOTE: If you don’t need to reference a partial view after the fact,
    //you can simply instantiate an instance of it without assigning it within the object’s constructor, as so:
    new pSidebar(pageTitle);
     
    //doing the same for the Partial Footer View
    this.footer = new pFooter();
     
    // binding events as soon as the object is instantiated
    this.bindEvents();
};

Как вы можете видеть, ссылаясь на объект Sidebar как локальное свойство объекта About, мы теперь связываем этот экземпляр, что является очень естественным поведением — этот экземпляр теперь является боковой панелью About Page.

Если вам не нужно ссылаться на частичное представление после факта, вы можете просто создать его экземпляр, не назначая его в конструкторе объекта, как показано ниже:

1
2
3
4
5
6
7
8
var About = function(pageTitle) {
    this.pageTitle = pageTitle;
     
    new pSidebar(pageTitle);
     
    // binding events as soon as the object is instantiated
    this.bindEvents();
};

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

1
2
3
4
5
<script src=»/path/to/scripts/views/about.js»></script>
<script src=»/path/to/scripts/partials/sidebar.js»></script>
<script>
    new About(«About Us»);
</script>

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

  1. Номенклатура облегчает навигацию по коду
  2. Мы естественным образом присваиваем пространство имен нашим объектам, уменьшая необходимость в длинных именах методов и чрезмерное использование анонимного закрытия.
  3. В другом коде практически нет конфликтов, потому что наши методы находятся на прототипе объекта, а не на глобальном уровне
  4. При создании экземпляров наших частичных представлений в конструкторе объектов нашего представления и назначении их для ссылки на локальную переменную мы фактически создаем локально связанную копию объекта этого частичного представления.
  5. У нас есть четкое определение контекста, и мы можем без проблем использовать ключевое слово «это».
  6. Отладка становится понятной, потому что все методы, показанные в стеке, связаны в одном месте.

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

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