Мы создаем множество поисковых приложений, в том числе наш инструмент поиска по релевантности, Quepid или Глобальный поиск по патентным навигаторам в отделениях по патентам и товарным знакам США . Большая часть осчастливления клиентов заключается в том, чтобы сделать веб-интерфейс доступным для поиска! При этом мы любим Angular, но нам, безусловно, пришлось остерегаться одного антишаблона производительности — чрезмерной грязной проверки.
Angular обеспечивает удобную двустороннюю привязку данных. Это чрезвычайно удобный способ создания богатых шаблонов, которые каким-то волшебным образом знают, что нужно обновляться в DOM. Я легко могу написать некоторый декларативный HTML, который выражает контент, который я пытаюсь вывести на экран:
<h3>{{companyName}} Employees</h3> <ul> <li ng-repeat="employee in employees">{{employee.fullname()}}</li> </ul>
Вместо того, чтобы вручную вводить команду для обновления шаблона, что-то волшебное в Angular каким-то образом знает, что переменные, связанные с текущей областью $ (в данном случае {{companyName}} и {{employee}} ), изменились — и, следовательно, связанные фрагменты HTML необходимо обновить.
Грязный (проверяющий) секрет двустороннего связывания …
Как Angular волшебным образом знает, как обновить контент? Он использует технику, называемую грязной проверкой. Angular отслеживает предыдущие значения для этих различных выражений. Если он замечает изменение, соответствующая угловая директива дает возможность отразить эти изменения в DOM. Поэтому, если employee.fullname () изменяется, директива интерполяции (причудливое имя для синтаксического сахара с двойными фигурными скобками) имеет шанс отразить это изменение в DOM.
Вся эта проверка происходит методами $ scope. Цикл дайджеста $ scope выполняет грязную проверку, оценивая выражения в $ scope, сверяя их с предыдущими значениями и перезаписывая предыдущие значения только что вычисленными. Этот цикл дайджеста запускается угловыми директивами. Например, ng-click — это просто директива, которая связывается с событием click соответствующего элемента. При щелчке директива оценивает выражение, возможно, вызывает функцию или выполняет простое присваивание. Например, возможно, щелчок заставил переменную «login» установить в «true». Как только какие-либо данные были изменены, прямой может запустить цикл дайджеста (через $ scope. $ Apply).
На приемном конце двусторонней привязки находятся биты углового кода (директивы или иные), ожидающие ответа на изменения. Это то, что вы видите в своем англоязычном HTML с такими выражениями, как ng-repeat="employee in employees"
. В конечном итоге эти директивы регистрируются с помощью $ scope. $ Watch (эти выражения проверяются на наличие ошибок). Задача цикла дайджеста — идентифицировать все выражения, зарегистрированные для просмотра (через $ scope.watch), посмотреть, есть ли изменения, и выполнить зарегистрированный обратный вызов. У нас может быть $ scope. $ Watch на флаге входа из предыдущего абзаца. Наш обратный вызов может обновить DOM или перейти на новую страницу.
Это и секретный соус Angular, и ахиллесова пята. С большинством приложений это, вероятно, не проблема. Однако в случае с Quepid у нас была проблема с повторяющимися элементами, на которые можно было нажать. Как только вы оказались внутри каждого элемента, вы можете взаимодействовать с десятками маленьких виджетов.
С точки зрения реализации, каждый из этих виджетов был скрыт до тех пор, пока на них не нажали, что-то вроде:
<div ng-show="isClicked"> ... </div>
К сожалению, «скрытый» не мешает десяткам маленьких виджетов Angular регистрироваться в своих областях и проверяться на грязность во время цикла дайджеста. После достаточного количества этих повторяющихся элементов приложение будет значительно тормозить. Используя профилировщик, мы могли ясно видеть, что проблема была в $ scope.digest — цикле дайджеста Angular.
Итак, что можно сделать для решения этой проблемы? Позвольте мне рассказать вам о некоторых стратегиях, которые сработали для нас.
1. Используйте нг-если не нг-шоу
Одна из лучших функций Angular 1.2 — это ng-if. ng-show работает, применяя «display: none» к элементу или выключая его — таким образом скрывая его, но сохраняя его в DOM. К сожалению, сохраняя это в DOM, директивы внутри все еще ждут изменений и делают работу. Ng-if, с другой стороны, задерживает компиляцию элементов в DOM, пока условие не станет истинным. По сути, это способ ленивой загрузки угловых возможностей в DOM и, таким образом, избежание сотен бессмысленных грязных проверок, замедляющих работу приложения.
Недостатком является то, что ng-if требует небольшого снижения производительности, поскольку он должен полностью компилировать шаблон Angular по требованию. Это может быть смягчено комбинацией Ng-show и Ng-if. Что если Ng-if был связан с состоянием наведения, но в сочетании с щелчком Ng, который был более тесно связан с состоянием щелчка?
<div ng-if="hover" ng-show="clicked" ...>
Возможно, будущие версии Angular могли бы объединить некоторые ленивые функции загрузки из ng-if с новым шикарным shadow-DOM, который каким-то образом мог выполнять часть работы, не регистрируя все маленькие выражения, которые должны следить за изменениями в DOM.
2. Убедитесь, что проверяется дешево
При частой грязной проверке нецелесообразно помещать вызовы сложных функций в выражения Angular. Помните, этот материал будет называться много! NG-щелчок где-нибудь может вызвать много грязных проверок. Таким образом, следует избегать любых длительных расчетов. Например, сортировка большого списка и возврат верхнего элемента, вероятно, будет большим нет-нет. Вместо этого, есть способ проверить, изменился ли список, а затем выполнить какое-то действие (см. № 3).
3. Для ручных часов используйте одну версию или хэш (не много маленьких часов)
Вместо
$scope.$watch("employee.firstName", function() {...}) $scope.$watch("employee.lastName", function() {...})
Объедините их в одно наблюдение за хешем или другим значением, которое будет меняться при каждом изменении состояния. Очевидное решение — дать сотруднику функцию хеширования, которая всегда возвращает уникальное значение:
Employee.Hash = function() { return this.firstName + this.lastName + this.Age ...; }
Тогда в другом месте:
$scope.$watch("employee.hash", function() { });
Хеш-функции могут сталкиваться. Они также могут быть дорогими. Поэтому в последнее время я использую номер версии, который увеличивается при каждом изменении для этих проблем:
this.setFirstName = function(newFirstName) { this.firstName = newFirstName this.version++; }
Это бремя реализации, но позвольте мне написать код, который просто проверяет, изменилось ли одно целое число:
4. Не используйте $ scope. $ Watch для событий
Когда вы начинаете с Angular, заманчиво передавать события вверх и вниз по иерархии $ scope, устанавливая статус на одном уровне и выполняя $ watch для него в другом месте. Например, в корневой области мы могли бы установить:
$scope.userLogedIn = true
И в детских областях мы могли бы хотеть ответить:
$scope.$watch("userLoggedIn", function() { });
В качестве альтернативы, не загромождайте цикл дайджеста выражениями, похожими на события. Используйте явные функции событий Angular, такие как emit и broadcast.
5. Избегайте грязной проверки полностью, работая прямо с DOM
One of my favorite things about Angular is that I reserve the right to not use Angular if need be. Even in Angular world, I can simply use a directive’s link function to interact directly with the DOM as needed. For example, I could chose to simply insert a handlebars template into a div instead of worrying about getting angular-ese right:
link: function(scope, element) { button = element.find("button"); button.onClick = function() { // find our list! employeeList = element.find("employeeList"); employeeList.innerHtml = "<li>No Employees, Only Ninjas!</li>" }; };
Anyway, those techniques have helped us tremendously in our work. I hope you’ll also find them useful. Please comment if you have anything you’d add to this list! And of course if you have problems with your search UI or would simply like a better search application, contact us to see if we can help!