Интерфейсы. Прочь от MVC

  1. Два слова об MVC
  2. Особенности браузера
    1. Важно количество кода
    2. Требования по производительности
    3. Богатое(Rich) представление
    4. Минусы использования DOM/View как Model
      1. Скорость доступа к свойствам элементов DOM
      2. Утечки памяти при коммуникации DOM-javascript
      3. Когда эти минусы значимы?
  3. Компромиссы
    1. Общий объект-менеджер
    2. Паттерн Flyweight (приспособленец)
    3. Частичное отделение
  4. Заключение

Большинство сложных программных систем создаются с использованием паттерна MVC.

Многие программисты, приходя на javascript с других языков, по инерции используют этот подход.

Однако, при программировании javascript-интерфейса он зачастую бесполезен, приводит к тормозам, переусложнению приложений…

В javascript-интерфейсах, в отличие от Java/C++ и других — обычно не нужен паттерн MVC.

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

Модель (Model)
Собственно данные, методы для работы с данными, изменения и обновления данных.
Представление/Вид (View)
Отображение данных, оформление и другие аспекты презентации модели
Контроллер (Controller)
Реагирует на действия пользователя, интерпретирует данные, введенные пользователем, и информирует модель и производит необходимые манипуляции с моделью и видом.

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

Главный тезис статьи можно сформулировать так:

Controller пусть остается отдельно, а Model от View отделять не надо. Особенности Javascript/DOM/CSS позволяют успешно реализовывать аспекты Model средствами View.

Дальнейшая часть статьи — обоснование с примерами, почему именно так, а не иначе, и почему устоявшаяся практика MVC для javascript-интерфейсов работает плохо.

Никто не говорит о том, что MVC вообще — это плохо. Наоборот, MVC — это хорошо. Но вся штука в том, что при программировании для Web есть как минимум 3 важных особенности.

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

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

В самом деле, если интерфейс заведомо простой (и таким останется) — то лишние паттерны тем более не нужны.

Javascript/DOM в браузере — не самая быстрая платформа, а приложения фактически являются real-time, т. е интерфейс должен реагировать и отображаться по возможности мгновенно, плавно, и не сильно жрать память.

Межкомпонентные коммуникации Model-View добавляют тормозов. Это повод их убрать.

Далее, и модель и вид в javascript, как правило, оперируют данными в одинаковом формате

.

То есть, значение в форме input.value — не рисованный объект типа GraphicsTTFString, как это может быть в обычном десктоп-програмировании, не кодированная строка пикселей, а обычный текст.

Поэтому отделение Model от View приводит к излишнему дублированию данных.

Что такое View в javascript-интерфейсах?

Это в первую очередь DOM и CSS. Модель DOM заранее включает в себя следующее:

При этом основные аспекты оформления задаются вообще отдельно от DOM, при помощи CSS.

Вы видите, DOM — это совсем не тот «классический» View, который обычно имеется в виду. Он гораздо мощнее.

Так зачем нам поддерживать иерархию, средства выборки и контейнеры для свойств в специальных javascript-объектах Модели, если с этим замечательно справляется DOM/View ?

Минусы, как и плюсы, связаны с производительностью.

Скорость доступа к свойствам элементов DOM

При работе исключительно с javascript, работа идет в едином пространстве интерпретатора javascript. Это куда быстрее, чем обращаться к DOM-объектам, который в Microsoft относятся к COM, в Firefox — к XPCOM, в общем — живут отдельно от javascript-движка.

Эта проблема раньше была очень актуальна для Internet Explorer.

На момент написания статьи она фактически решена как библиотечными средствами (clobbering, учет и зачистка назначаемых DOM-элементам свойств), так и патчами.

В частности, как в IE7, так и в IE6 с обновлениями середины 2007 связка DOM-javascript почти не течет.

При разработке приложений надо смотреть, насколько тесно изменение ее модели (данных) завязано на изменение представления (DOM).

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

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

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

Современные js-фреймворки (YUI, dojo, jQuery) ликвидируют бОльшую часть утечек памяти.

Полностью впихивать Model во View, конечно, не обязательно. Да оно обычно и не нужно.

Можно выделить три устоявшихся практики.

Создается единый объект, который существует над компонентами интерфейса и хранит данные всех моделей, задействованных в данном интерфейсе.

Например, во вложенном меню это будет единый объект Menu, который умеет show/hide любые подменю. При этом подменю как таковое не является javascript-объектом.

Аналогично, в javascript-дереве это может быть единый объект Tree, который манипулирует узлами, а сами узлы — просто элементы DOM и хранят данные непосредственно в DOM.

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

Например, рассмотрим контекстное меню для javascript-дерева, которое вызывается правым кликом мыши на элементе.

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

При клике на узел:

  1. ContextMenu инициализуется нужным узлом
  2. В зависимости от узла и прав на этот узел включает-выключает пункты подменю
  3. Показывается на нужном месте экрана

Приспособление ContextMenu к конкретному элементу выполняется очень просто. В данном случае — не нужно создавать DOM-структуру меню, она и так есть. Не надо и создавать новый объект со своими методами — достаточно тех что есть.

Наконец, иногда целесообразно частично отделить некоторые аспекты Model от View.

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

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

Их применение ведет к уменьшению и упрощению кода. Успехов.

JavaScript и AJAX в ASP.NET MVC 5

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Современные веб-приложения практически невозможно представить без языка клиентской части — JavaScript. Даже при использовании таких серверных языков и технологий, как PHP, ASP.NET, трудно обойтись без JavaScript. Однако чистый JavaScript в реальности используется все меньше. Ему на смену приходят специальные библиотеки, в частности, jQuery. Применительно к ASP.NET MVC при создании веб-приложений библиотеки jQuery играют очень большую роль.

Подключение файлов JavaScript/jQuery

По умолчанию проект ASP.NET MVC 5 уже содержит необходимый базовый набор скриптов, в том числе библиотеки jQuery:

Большинство скриптов по умолчанию имеют свои двойники с суффиксом min, например, jquery-1.10.2.js и jquery-1. 10.2.min.js. Оба скрипта представляют одну и ту же функциональность. Но вторая версия представляет минимизированную версию (поэтому и идет с суффиксом min). Подобные минимизированные скрипты гораздо меньше по объему (в среднем на 60%), поэтому в реальным приложениях предпочтительнее использовать именно минимизированные скрипты, так как пользователь тратит меньше времени и трафика на их загрузку. В то же время их не очень удобно читать. Поэтому для большего удобства разработчиков полные и минимизированные скрипты базовых библиотек идут вместе.

Вкратце посмотрим, зачем нужны большинство скриптов, идущих по умолчанию в проекте MVC 5 с типом аутентификации Individual User Accounts:

  • jquery-1.10.2.js — базовая библиотека jQuery, на которую опираются большинство других скриптов. В данном случае используется версия 1.10.2. Однако библиотека постоянно обновляется, поэтому можно использовать более новые версии, которые можно добавить вручную или через NuGet.

  • modernizr-2.6.2.js — библиотека, позволяющая определить, поддерживает ли браузер те или иные возможности HTML5 и CSS3

  • bootstrap.js — библиотека, позволяющая создавать адаптивные веб-приложения с использованием css-фреймворка bootstrap

  • respond.js — позволяет использовать правила media queries CSS3 в старых браузерах, которые напрямую не поддерживают данную возможность

  • jquery.validate.js — представляет функционал для валидации на стороне клиента

  • jquery.validate.unobtrusive.js — предоставляет поддержку ненавязчивой валидации модели

  • jquery-1.10.2.intellisense.js и jquery.validate-vsdoc.js — используются для поддержки документации и IntelliSense по соответствующим библиотекам в Visual Studio

В некоторых типах проектов ряд распространенных скриптов может отсутствовать, например, если делать проект MVC 5 с типом аутентификации No Athentication, то в нем будет отсутствовать скрипты валидации. В этом случае мы можем найти недостающий скрипт в NuGet и добавить его в проект:

Чтобы подключить файл javascript используется метод Render класса System.Web.Optimization.Scripts:

@Scripts.Render("~/scripts/jquery.validate.min.js")

Этот метод принимает в качестве параметра строку — полный путь к скрипту.

Также для подключения скриптов мы можем использовать хелпер Url.Content:

<script src="@Url.Content("~/scripts/jquery.validate.min.js")" type="text/javascript"></script>

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


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
	@Scripts. Render("~/scripts/jquery-1.10.2.min.js")
    @Scripts.Render("~/scripts/validate.min.js")
</head>
<body>
    @RenderBody()
    @RenderSection("scripts", required: false)
</body>
</html>

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


@{
    ViewBag.Title = "Index";
}
<!--Далее основное содержание представления-->

<!--секция скриптов-->
@section Scripts {
    @Scripts.Render("~/scripts/validate.min.js")
}

НазадСодержаниеВперед

Как работает JavaScript: модульность и возможность повторного использования с MVC | by Ukpai Ugochi

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

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

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

MVC означает Model , View и Controller . Это архитектура программного обеспечения, которая разделяет логику программы на три взаимосвязанных компонента, то есть компоненты модели, представления и контроллера. Причина такого разделения заключается в создании индивидуального представления данных от пользовательского ввода до представления данных пользователям. Это означает, что архитектура MVC позволяет разработчикам отделить способ сбора данных от пользователей и способ представления данных пользователям.

Эта архитектура впервые стала популярной среди разработчиков Java, когда WebObjects был перенесен на Java. Затем такие языки, как JavaScript, создали несколько веб-фреймворков с архитектурой MVC для обеспечения соблюдения шаблона MVC. Фреймворки JavaScript, поддерживающие архитектуру MVC, в основном разбивают MVC на клиента и сервер, используя подход тонкого клиента.

Архитектура MVC очень полезна в JavaScript, поскольку она предлагает больше, чем просто возможность разработчикам создавать модульный код. Например, поскольку Модель в MVC возвращает данные без форматирования, одни и те же компоненты могут вызываться для использования в разных интерфейсах. Это позволяет повторно использовать код.

Далее давайте подробно рассмотрим архитектуру MVC и то, как она работает.

В этом разделе мы рассмотрим различные компоненты архитектуры MVC.

Модель

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

Например, данные пользовательского ввода (API, объекты JSON и т. д.) отправляются в модель. Затем модель управляет этими данными и отправляет их в представление.

View

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

Контроллер

Компонент Контроллер является связующим звеном между компонентами Модель и Представление. Этот компонент является мозгом вашего приложения, он отвечает за преобразование ввода из View в команды. Эти команды определяют, как информация отображается в View или обрабатывается в Model .

В качестве примера мы создадим простое приложение JavaScript, чтобы проиллюстрировать, как архитектура MVC работает в JavaScript.

Здесь мы будем использовать MongoDB в качестве нашей базы данных, шаблон EJS для просмотра и Node.js/Express для контроллеров. Сначала мы создадим приложение Node.js/Express и создадим точку входа нашего приложения с именем index.js

. Затем мы создадим нашу модель, в которую мы будем отправлять данные. Для этого мы будем использовать Mongoose для создания схемы данных того, как мы хотим, чтобы наши данные были структурированы в базе данных. Итак, мы создадим папку с именем models . В папке с моделями мы создадим Файл User.js . Этот файл будет содержать схему данных для нашего пользователя.

Далее мы создадим файл в корне нашего проекта и назовем его db.js . Этот файл будет отвечать за подключение нашего приложения к MongoDB:

Теперь мы создадим наш контроллер. Контроллер будет посредником между Моделью и Представлением, он будет содержать логику нашего приложения. Создайте папку в корне вашей папки и назовите ее controllers . В controllers , мы создадим файл с именем Usercontroller.js . Вставьте приведенный ниже код в свой файл Usercontroller.js :

Теперь мы создадим наше представление. Мы создадим папку с именем Views . В нашей папке Views мы создадим три файла для наших разных представлений. Файлы будут называться home.ejs , login.ejs и register. ejs . В вашем home.ejs введите код ниже:

 Это домашняя страница 

Поместите код ниже в ваш login.ejs :

Наконец, в ваш файл register.ejs поместите код ниже:

Чтобы добавить стили в наше приложение, создайте файл с именем style.css и вставьте код: ниже:

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

 node index.js 

В приведенном выше примере мы разделили наше приложение на представления, модели и контроллеры. Отделение бизнес-логики от представлений.

Существуют различные варианты архитектуры MVC. В этом разделе будут рассмотрены варианты архитектуры MVC и их применение в JavaScript.

Контроллер представления иерархической модели (HMVC)

Иногда в веб-приложениях необходимо оставлять комментарии и отвечать на них. Например, в блоге нужно будет отвечать на вопросы или комплименты гостей. Чтобы сделать это успешно, должна быть виджетизация структур контента. Обычный MVC не дает места для виджетизации структур контента. HMVC позволяет создавать виджет, который можно размещать на разных страницах, а не создавать/импортировать MVC на каждую страницу. Например, на платформе онлайн-покупок у вас может быть кнопка «Добавить в корзину» на главной странице, странице продукта и т. д. в виде виджета. Независимо от страницы, как только пользователь нажмет кнопку «Добавить в корзину», товар будет добавлен в его корзину в последовательном порядке. Этот метод устраняет необходимость создания новой кнопки MVC и добавления в корзину с нуля для каждой страницы. Это будет сложно, отнимет много времени и чревато проблемами. HMVC обеспечивает модульность и возможность повторного использования кодов. Содержимое отображается с использованием иерархической модели.

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

В приведенном выше примере разработчик добавляет виджет, который позволяет пользователю добавлять товар в корзину с любой страницы (домашней страницы/страницы товара). Товар будет отображаться на странице корзины (в нашем случае на веб-консоли) в соответствии с порядком его добавления. HMVC — это просто расширенная версия MVC, которая может иметь более одного контроллера. Например, разработчик может добавить другие контроллеры для удаления элементов корзины и обновления элементов корзины и т. д. с помощью HMVC, что обычно будет сложно для MVC.

Адаптер представления модели (MVA)

В простой модели модель-представление-контроллер данные передаются из контроллера в модель. Однако модель MVA делает контроллер посредником. Следовательно, модель и представление должны проходить через контроллер. Это делает невозможным взаимодействие представления и модели напрямую, как в модели MVC. Контроллеры называются адаптерами.

Хотя этот метод может показаться строгим, поскольку представление и модель должны проходить через контроллер, это упрощает отладку. Отличие этого варианта MVC от других заключается в возможности иметь несколько адаптеров в MVA. Простая иллюстрация архитектуры MVA показана в примере ниже.

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

Представление модели (MVP)

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

Эта модель похожа на модель MVA, поскольку представление и модель не взаимодействуют напрямую. Представление и модель должны проходить через Presenter.

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

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

Представление модели ViewModel (MVVM)

Model-View-ViewModel — это вариант модели MVC, который полностью отделяет пользовательский интерфейс (пользовательский интерфейс) от бизнес-логики. Архитектура MVVM основана на шаблонах MVC и MVP.

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

Эта архитектура позволяет разработчикам выполнять сборку одновременно. Разница между этой архитектурой и обычными MVC и MVP заключается в наличии связывателя. Биндер избавляет от необходимости писать стандартную логику, которая синхронизирует ViewModel и View.

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

Из приведенного выше примера наша Модель относится к классу 9.0055 Модель , состоящая из текста Введите что-то . Этот текст будет исходным текстом в поле ввода до того, как наш пользователь введет текст по своему выбору. Модель также имеет наблюдателя , который наблюдает за всеми изменениями, происходящими в Модели. Наша ViewModel получает текущее состояние модели и, как только происходит изменение, обновляет представление (второе поле ввода). Наше представление представляет собой HTML-код с двумя полями ввода. Одно поле позволяет пользователю вводить данные, а второе поле отображает введенные пользователем данные.

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

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

Существует множество фреймворков JavaScript, таких как Vue.js, Angular, ember.js и т. д. В этом разделе мы рассмотрим популярные фреймворки JavaScript, реализующие архитектуру MVC.

Vue.js

Vue.js — это среда JavaScript, позволяющая создавать интерактивные веб-интерфейсы. Vue.js реализует вариант архитектуры MVC MVVM. Он соединяет View с ViewModel с использованием двусторонней привязки данных. Здесь экземпляр vue:

 var vm = new Vue({ /* options */ }) 

действует как ViewModel. Он отвечает за привязку представления (DOM, управляемого экземплярами Vue) vm.$el к модели vm.$data , которые являются объектами данных.

На приведенной выше диаграмме видно, как вводимые пользователем данные передаются из представления через модель представления в модель и обратно в представление через модель представления. Слушатели DOM обрабатывают входные данные из представления в модель, в то время как директивы (команды, прикрепленные к элементам DOM, например, v-for) обрабатывают данные из модели в представление.

Angular

Архитектура MVC в среде angular довольно проста. Архитектура основана на HTML и JavaScript. Контроллеры, написанные на JavaScript, собирают пользовательский ввод/события из представления, обрабатывают их и отправляют в модель. Из модели информация отправляется в представление.

Как и Vue.js, Angular использует директивы. Директивы в Angular могут изменять DOM.

Ember.js

Ember.js — это еще одна среда JavaScript, позволяющая использовать архитектуру MVC при создании приложений. В Ember.js привязки позволяют создавать приложения с архитектурой MVC. Handlebars действуют как View в Ember.js, а Controller — это логика JavaScript, которая определяет, как ваше приложение будет функционировать. Пользовательские вводы/события передаются из представлений в контроллер, где они обрабатываются, а данные отправляются в модель.

Хотя архитектура Ember.js MVC похожа на Rails, они отличаются. Поскольку Ember.js работает на стороне клиента, он может получать пользовательский ввод и события. Однако Rails, являющийся бэкэнд-фреймворком, получает ввод в виде HTTP-запросов.

Maria.js

Maria.js — это среда JavaScript, реализующая архитектуру MVC. События/ввод из представления отправляются в контроллер, где они обрабатываются, а данные отправляются в модель. В Maria.js у разработчиков есть возможность определить методы обработчика, если они не хотят, чтобы контроллер обрабатывал необработанный DOM. При этом вы можете обойтись без контроллеров и позволить представлению обрабатывать все пользовательские события.

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

Для этого разработчики могут использовать архитектуру MVC. В веб-приложениях разработчики используют модель тонкого клиента, при которой данные с клиентской стороны приложения отправляются на сервер. Сервер отвечает за управление/обработку данных.

Примером может служить стек MEAN или MEVN. Здесь данные со стороны клиента (Angular или Vue) отправляются в Node.js/Express.js, который обрабатывает данные и отправляет их в базу данных (MongoDB). Если данные не подходят для отправки в базу данных, они обрабатываются и отправляются обратно в представление (на стороне клиента), в противном случае они отправляются в базу данных, где при необходимости их можно вызвать в представлении. Клиентская сторона действует как представление, поскольку она принимает пользовательский ввод и отображает информацию с сервера посредством привязки данных. Кроме того, Node.js/Express.js выступает в роли контроллера, и внутри него прописывается логика приложения. Он отправляет данные в базу данных MongoDB, откуда их можно получить в представлении.

Реальным примером стека MEAN является SessionStack (хотя в стек также включено множество других технологий). Клиентская часть платформы построена на Angular и использует шаблон MVC, предоставляемый фреймворком. Это позволило создать сложный, но интуитивно понятный пользовательский интерфейс, который позволяет командам воспроизводить пути пользователей в виде видеороликов, чтобы оптимизировать рабочие процессы продукта.

Существует бесплатная пробная версия, если вы хотите попробовать SessionStack.

SessionStack воспроизводит сеанс

Если вы пропустили предыдущие главы серии, вы можете найти их здесь:

  • Обзор движка, среды выполнения и стека вызовов
  • Внутри движка Google V8 + 5 советов по написанию оптимизированного кода
  • Управление памятью + как справляться с 4 распространенными утечками памяти
  • Цикл событий и подъем асинхронного программирования + 5 способов улучшить кодирование с помощью async/await
  • Глубокое погружение в WebSockets и HTTP/2 с SSE + как выбрать правильный путь
  • Сравнение с WebAssembly + почему в некоторых случаях лучше использовать его вместо JavaScript
  • Составные части Web Workers + 5 случаев, когда их следует использовать
  • Service Workers, их жизненный цикл и варианты использования
  • механика веб-push-уведомлений
  • Отслеживание изменений в DOM с помощью MutationObserver
  • Механизм рендеринга и советы по оптимизации его производительности
  • Внутри сетевого уровня + как оптимизировать его производительность и безопасность
  • Под капотом анимации CSS и JS + как оптимизировать их производительность
  • Синтаксический анализ, абстрактные синтаксические деревья (AST) + 5 советов о том, как минимизировать время синтаксического анализа
  • Внутреннее устройство классов и наследования + транспиляция в Babel и TypeScript
  • Механизмы хранения + как правильно выбрать API хранилища
  • Внутреннее устройство Shadow DOM + как создавать автономные компоненты
  • WebRTC и механика однорангового подключения
  • Под капотом пользовательских элементов + Передовой опыт построения многоразовые компоненты
  • Исключения + лучшие практики для синхронного и асинхронного кода
  • 5 типов XSS-атак + советы по их предотвращению
  • CSRF-атаки + 7 стратегий смягчения последствий
  • Итераторы + советы по расширенному контролю над генераторами
  • Криптография + как бороться атаки «человек посередине» (MITM)
  • Функциональный стиль и его сравнение с другими подходами
  • Три типа полиморфизма
  • Регулярные выражения (RegExp)
  • Введение в Deno
  • Шаблоны креативного, структурного и поведенческого проектирования + 4 передовых метода

Создание простого приложения MVC с нуля на JavaScript

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

Я сделал это приложение todo, которое представляет собой простое небольшое браузерное приложение, которое позволяет вам CRUD (создавать, читать, обновлять и удалять) todos. Он просто состоит из index.html , style.css и script.js , такие приятные и простые, без зависимостей и фреймворков для учебных целей.

Предварительные условия
  • Базовый JavaScript и HTML
  • Знакомство с последним синтаксисом JavaScript
Цели

Создайте приложение todo в браузере с помощью простого JavaScript и ознакомьтесь с концепциями MVC (и ООП — объектно-ориентированного программирования).

  • Посмотреть демонстрацию
  • Посмотреть исходный код

Примечание . Поскольку это приложение использует новейшие функции JavaScript (ES2017), оно не будет работать в исходном виде в некоторых браузерах, таких как Safari, без использования Babel для компиляции в обратно совместимый синтаксис JavaScript.

Что такое контроллер представления модели?

MVC — это один из возможных шаблонов для организации вашего кода. Это популярный.

  • Модель — управляет данными приложения
  • Вид — Визуальное представление модели
  • Контроллер — связывает пользователя и систему

Модель это данные. В этом приложении todo это будут сами todo и методы, которые будут добавлять, редактировать или удалять их.

Представление — это способ отображения данных. В этом приложении todo это будет отображаемый HTML в DOM и CSS.

Контроллер соединяет модель и вид. Он принимает пользовательский ввод, такой как щелчок или ввод текста, и обрабатывает обратные вызовы для взаимодействия с пользователем.

Модель никогда не касается вида. Вид никогда не касается модели. Контроллер их соединяет.

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

Первоначальная установка

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

index.html

 

  <голова>
    <метакодировка="utf-8" />
    
    
    Приложение Todo
    <ссылка rel="stylesheet" href="style.css" />
  
  <тело>
    <дел>
    <скрипт src="script.js">
  
 

Я написал небольшой фрагмент CSS, чтобы он выглядел приемлемо, который вы можете найти здесь и сохранить в style. css . Я не буду больше писать о CSS, потому что это не тема этой статьи.

Хорошо, теперь, когда у нас есть HTML и CSS, самое время приступить к написанию приложения.

Приступая к работе

Мы собираемся сделать это действительно красиво и просто, чтобы понять, какой класс относится к какой части MVC. Я сделаю Модель класс, View class и Controller class, который принимает модель и представление. Приложение будет экземпляром контроллера.

Если вы не знакомы с тем, как работают классы, прочтите статью Общие сведения о классах в JavaScript.

 класс Модель {
  конструктор () {}
}
вид класса {
  конструктор () {}
}
класс Контроллер {
  конструктор (модель, вид) {
    эта.модель = модель
    this.view = вид
  }
}
const app = new Controller(new Model(), new View()) 

Очень красиво и абстрактно.

Модель

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

Модель

 Класс Модель {
  конструктор () {
    // Состояние модели, массив объектов todo, предварительно заполненный некоторыми данными
    это.todos = [
      {id: 1, text: 'Пробегите марафон', завершено: false},
      {id: 2, текст: 'Посадить сад', завершено: false},
    ]
  }
  добавитьTodo(todoText) {
    константа todo = {
      идентификатор: this.todos.length > 0? this.todos[this.todos.length - 1].id + 1 : 1,
      текст: todoText,
      полное: ложное,
    }
    this.todos.push(дело)
  }
  // Сопоставить все задачи и заменить текст задачи указанным идентификатором
  editTodo (id, updatedText) {
    this.todos = this.todos.map((todo) =>
      todo.id === идентификатор ? {id: todo.id, text: updatedText, Complete: todo.complete} : todo,
    )
  }
  // Отфильтровать задачу из массива по id
  удалитьTodo (идентификатор) {
    this.todos = this.todos.filter((todo) => todo.id !== id)
  }
  // Перевернуть полное логическое значение для указанной задачи
  toggleTodo (идентификатор) {
    this. todos = this.todos.map((todo) =>
      todo.id === идентификатор ? {id: todo.id, text: todo.text, Complete: !todo.complete} : todo,
    )
  }
} 

У нас есть addTodo , editTodo , deleteTodo и toggleTodo . Все они должны быть очень понятными — add добавляет новую задачу в массив, edit находит идентификатор задачи для редактирования и заменяет ее, delete отфильтровывает задачу из массива и тумблер переключает логическое свойство complete .

Поскольку мы делаем все это в браузере, а приложение доступно из окна (глобально), вы можете легко проверить это, набрав что-то вроде:

 app.model.addTodo('Вздремнуть') 

добавит задачу в список, и вы сможете зарегистрировать содержимое app.model.todos .

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

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

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

Представление

Мы собираемся создать представление, манипулируя DOM — объектной моделью документа. Поскольку мы делаем это на простом JavaScript без помощи React JSX или языка шаблонов, это будет многословно и некрасиво, но такова природа непосредственного манипулирования DOM.

Ни контроллер, ни модель не должны ничего знать о DOM, HTML-элементах, CSS или обо всем этом. Все, что с этим связано, должно быть в поле зрения.

Если вы не знакомы с DOM или чем DOM отличается от исходного кода HTML, прочтите Введение в DOM.

Первое, что я сделаю, это просто создам вспомогательные методы для извлечения элемента и создания элемента.

Просмотр

 Класс Просмотр {
  конструктор () {}
  // Создаем элемент с необязательным классом CSS
  createElement(тег, className) {
    константный элемент = document.createElement(тег)
    если (имя класса) element.classList.add(имя класса)
    возвращаемый элемент
  }
  // Получить элемент из DOM
  получитьЭлемент(селектор) {
    константный элемент = document.querySelector (селектор)
    возвращаемый элемент
  }
} 

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

  • Корневой элемент приложения — #root
  • Заголовок заголовка — h2
  • Форма, кнопка ввода и отправки для добавления задачи — форма , ввод , кнопка
  • Список дел — ул

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

Просмотр

 Класс Просмотр {
  конструктор () {
    // Корневой элемент
    this.app = this.getElement('#root')
    // Название приложения
    this.title = this.createElement('h2')
    this.title.textContent = 'Все дела'
    // Форма с вводом [type="text"] и кнопкой отправки
    this.form = this.createElement('форма')
    this.input = this.createElement('input')
    this.input.type = 'текст'
    this.input.placeholder = 'Добавить задачу'
    this.input.name = 'дело'
    this.submitButton = this.createElement('кнопка')
    this.submitButton.textContent = 'Отправить'
    // Визуальное представление списка задач
    this.todoList = this.createElement('ul', 'список дел')
    // Добавляем кнопку ввода и отправки в форму
    this.form.append(this.input, this.submitButton)
    // Добавляем заголовок, форму и список дел к приложению
    this.app.append(this.title, this.form, this.todoList)
  }
  // ...
} 

Теперь настроены части вида, которые не будут изменяться.

Еще две мелочи — геттер и ресеттер входного (нового todo) значения.

Я использую символы подчеркивания в именах методов, чтобы показать, что это частные (локальные) методы, которые не будут использоваться вне класса.

Просмотр

 получить _todoText() {
  вернуть this.input.value
}
_resetInput () {
  это.ввод.значение = ''
} 

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

Просмотр

 displayTodos(todos) {
  // ...
} 

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

Первое, что мы сделаем, это удалим все узлы задач при каждом вызове. Затем мы проверим, существуют ли какие-либо задачи. Если они этого не сделают, мы отобразим сообщение о пустом списке.

Просмотр

 // Удалить все узлы
в то время как (this.todoList.firstChild) {
  this.todoList.removeChild(this.todoList.firstChild)
}
// Показать сообщение по умолчанию
если (todos.length === 0) {
  const p = this.createElement('p')
  p.textContent = 'Нечего делать! Добавить задачу?
  this.todoList.append(p)
} еще {
  // ...
} 

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

Просмотр

 еще {
  // Создаем узлы элементов задач для каждой задачи в состоянии
  todos.forEach(todo => {
    const li = this.createElement('li')
    li.id = todo.id
    // Каждый элемент списка дел будет иметь флажок, который вы можете переключить
    const флажок = this.createElement('input')
    checkbox.type = 'флажок'
    checkbox.checked = todo.complete
    // Текст элемента списка дел будет находиться в редактируемом диапазоне
    const span = this. createElement('span')
    span.contentEditable = истина
    span.classList.add('редактируемый')
    // Если задача завершена, она будет зачеркнута
    если (todo.complete) {
      постоянный удар = this.createElement('s')
      strike.textContent = задача.текст
      span.append(забастовка)
    } еще {
      // В противном случае просто отображаем текст
      span.textContent = задача.текст
    }
    // В задачах также будет кнопка удаления
    const deleteButton = this.createElement('кнопка', 'удалить')
    deleteButton.textContent = 'Удалить'
    li.append (флажок, интервал, кнопка удаления)
    // Добавляем узлы в список задач
    this.todoList.append(li)
  })
} 

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

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

Контроллер

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

Контроллер

 класс Контроллер {
  конструктор (модель, вид) {
    эта.модель = модель
    this.view = вид
  }
} 

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

Контроллер

 класс Контроллер {
  конструктор (модель, вид) {
    эта.модель = модель
    this.view = вид
    // Отображаем начальные задачи
    this.onTodoListChanged(this.model.todos)
  }
  onTodoListChanged = (todos) => {
    this.view.displayTodos (задачи)
  }
} 

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

Создадим обработчики событий в контроллере.

Контроллер

 handleAddTodo = (todoText) => {
  this.model.addTodo(todoText)
}
handleEditTodo = (id, todoText) => {
  this.model.editTodo(id, todoText)
}
handleDeleteTodo = (id) => {
  this.model.deleteTodo(id)
}
handleToggleTodo = (id) => {
  this.model.toggleTodo(id)
} 

Настройка прослушивателей событий

Теперь у нас есть эти обработчики, но контроллер все еще не знает, когда их вызывать. Мы должны поместить прослушиватели событий в элементы DOM в представлении. Мы ответим на событие отправки в форме, нажмите и изменим события в списке дел. (Я пока пропускаю «Редактировать», так как это немного сложнее.)

View

 bindAddTodo(handler) {
  this.form.addEventListener('отправить', событие => {
    событие.preventDefault()
    если (это._todoText) {
      обработчик (это._todoText)
      это._resetInput()
    }
  })
}
bindDeleteTodo (обработчик) {
  this. todoList.addEventListener('щелчок', событие => {
    если (event.target.className === 'удалить') {
      const id = parseInt(event.target.parentElement.id)
      обработчик (идентификатор)
    }
  })
}
bindToggleTodo (обработчик) {
  this.todoList.addEventListener('изменение', событие => {
    если (event.target.type === 'флажок') {
      const id = parseInt(event.target.parentElement.id)
      обработчик (идентификатор)
    }
  })
} 

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

Мы использовали стрелочные функции для всех событий дескриптора. Это позволяет нам вызывать их из представления, используя контекст this контроллера. Если бы мы не использовали стрелочные функции, нам пришлось бы привязывать их вручную, например, this.view.bindAddTodo(this.handleAddTodo.bind(this)) . Угу.

Контроллер

 this.view.bindAddTodo(this. handleAddTodo)
this.view.bindDeleteTodo(this.handleDeleteTodo)
this.view.bindToggleTodo (это.handleToggleTodo)
// this.view.bindEditTodo(this.handleEditTodo) — мы сделаем это в последний раз 

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

Отвечать на обратные вызовы в модели

Мы что-то упустили — события прослушиваются, обработчики вызываются, но ничего не происходит. Это связано с тем, что модель не знает, что представление должно обновляться, и не знает, что делать, чтобы обновить представление. У нас есть метод displayTodos для представления, чтобы решить эту проблему, но, как упоминалось ранее, модель и представление не должны знать друг о друге.

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

Мы уже сделали метод onTodoListChanged на контроллере, чтобы справиться с этим, нам просто нужно сообщить об этом модели. Мы привяжем его к модели так же, как мы сделали это с обработчиками представления.

В модели добавьте bindTodoListChanged для onTodoListChanged .

Модель

 bindTodoListChanged(обратный вызов) {
  this.onTodoListChanged = обратный вызов
} 

И вы свяжете это с контроллером, как и с представлением.

Контроллер

 this.model.bindTodoListChanged(this.onTodoListChanged) 

Теперь после каждого метода в модели вы будете вызывать обратный вызов onTodoListChanged .

Модель

 удалитьTodo(id) {
  this.todos = this.todos.filter(todo => todo.id !== id)
  this.onTodoListChanged(this.todos)
} 

Добавить локальное хранилище

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

Если вы не знаете, как работает локальное хранилище, прочитайте «Как использовать локальное хранилище с JavaScript».

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

Модель

 Класс Модель {
  конструктор () {
    this.todos = JSON.parse(localStorage.getItem('todos')) || []
  }
} 

Мы создадим частный метод commit для обновления значения localStorage , а также состояния модели.

Модель

 _commit(todos) {
  this.onTodoListChanged(список дел)
  localStorage.setItem('todos', JSON.stringify(todos))
} 

После каждого изменения this.todos мы можем вызвать его.

Модель

 удалитьTodo(id) {
  this.todos = this.todos.filter(todo => todo.id !== id)
  this._commit(this.todos)
} 

Добавление функции редактирования в реальном времени

Последний элемент этой головоломки — возможность редактирования существующей задачи. Редактировать всегда немного сложнее, чем добавлять или удалять. Я хотел сделать его простым и не требовать кнопки редактирования или замены диапазона на ввод или что-то в этом роде. Мы также не хотим вызывать editTodo каждый раз, когда набирается буква, потому что это будет перерисовывать весь пользовательский интерфейс списка задач.

Я решил создать в представлении метод, который обновляет временную переменную состояния новым значением редактирования, а другой метод вызывает метод handleEditTodo в контроллере, который обновляет модель. Событие input запускается при вводе элемента contenteditable и focusout срабатывает, когда вы покидаете элемент contenteditable .

Представление

 конструктор() {
  // ...
  this._temporaryTodoText
  это._initLocalListeners()
}
// Обновить временное состояние
_initLocalListeners() {
  this.todoList.addEventListener('input', событие => {
    если (event.target.className === 'редактируемый') {
      this._temporaryTodoText = event.target.innerText
    }
  })
}
// Отправляем готовое значение в модель
bindEditTodo (обработчик) {
  this. todoList.addEventListener('focusout', событие => {
    если (это._temporaryTodoText) {
      const id = parseInt(event.target.parentElement.id)
      обработчик (идентификатор, this._temporaryTodoText)
      this._temporaryTodoText = ''
    }
  })
} 

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

Просто не забудьте привязать обработчик editTodo .

Контроллер

 this.view.bindEditTodo(this.handleEditTodo) 

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

Заключение

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