Speeding up AngularJS apps with simple optimizations
Speeding up AngularJS apps with simple optimizations
ng-newsletterで流れてきて結構いい記事だったのでメモ。
基本的には、$$watchers
を減らして、$digest
の負荷を下げましょうとのこと。
One Time Binding Syntax {{ ::value }}
1.3.0からの機能。
{{ ::value }}
と書くと最初の1度だけバインディングされ、後は$scope.value
が更新されても{{ ::value }}
は更新されない。バインディングされた後は監視から外れるので、$digest
時の負荷が下がる。
<!DOCTYPE html> <html ng-app="app"> <body> <div ng-controller="MainCtrl"> <h1>{{ title }}</h1><!-- ボタンを押すとどんどん増えていく --> <h1>{{ ::title }}</h1><!-- ボタンを押しても更新されない --> <button ng-click="plusTitle()">plus</button> </div> <script src="bower_components/angular/angular.min.js"></script> <script> angular.module('app', []) .controller('MainCtrl', function MainCtrl ($scope) { $scope.title = 'Hello!'; $scope.plusTitle = function () { $scope.title = $scope.title + ' ' + $scope.title; } }); </script> </body> </html>
$scope.$apply vs $scope.$digest
$scope.$apply
と$scope.$digest
は、Angularじゃないライブラリのイベント内でDOMを更新したりする場合に、その更新をAngular側にも通知するために使う。
$apply
の中で$digest
が呼ばれるからどっちも一緒だろと思ってたけど、違うらしい。
$scope.$apply
は$rootScope.$digest
を実行するので全ての$scope
で$digest
が実行されてしまい影響範囲が大きい。
$scope.$digest
はその$scope
と子孫の$scope
に$digest
が実行されるので、$scope.$apply
に比べると効率がよい。
<!DOCTYPE html> <body ng-app="app"> <div ng-controller="ParentScopeCtrl"> <h1>{{ parentObject.value }}</h1> <div ng-controller="ChildScopeCtrl1"> <h2>{{ parentObject.value }}</h2> <div ng-controller="ChildScopeCtrl2"> <h3>{{ parentObject.value }}</h3> </div> </div> <button id="call-apply-button">call $scope.$apply()</button> <button id="call-digest-button-1">call $scope.$digest() in ChildScopeCtrl1</button> <button id="call-digest-button-2">call $scope.$digest() in ChildScopeCtrl2</button> </div> <script src="bower_components/angular/angular.min.js"></script> <script> var $scopeOfChildScopeCtrl1, $scopeOfChildScopeCtrl2; angular.module('app', []) .controller('ParentScopeCtrl', function ($scope) { $scope.parentObject = { value: 'parent' }; }) .controller('ChildScopeCtrl1', function ($scope) { // angularの外で$scopeの中身を変更するために、変数に保持 $scopeOfChildScopeCtrl1 = $scope; }) .controller('ChildScopeCtrl2', function ($scope) { // angularの外で$scopeの中身を変更するために、変数に保持 $scopeOfChildScopeCtrl2 = $scope; }); // Angularの監視外でデータを変更し、$apply, $digestの違いを見る、 var callApplyButton = document.getElementById('call-apply-button'); var callDigestButton1 = document.getElementById('call-digest-button-1'); var callDigestButton2 = document.getElementById('call-digest-button-2'); callApplyButton.addEventListener('click', function (e) { // $apply => h1, h2, h3、全て更新される $scopeOfChildScopeCtrl1.parentObject.value = 'call $scope.$apply().'; $scopeOfChildScopeCtrl1.$apply(); }, false); callDigestButton1.addEventListener('click', function (e) { // $digest => h2, h3だけ更新される $scopeOfChildScopeCtrl1.parentObject.value = 'call $scope.$digest() in ChildScopeCtrl1.'; $scopeOfChildScopeCtrl1.$digest(); }, false); callDigestButton2.addEventListener('click', function (e) { // $digest => h3だけ更新される $scopeOfChildScopeCtrl2.parentObject.value = 'call $scope.$digest() in ChildScopeCtrl2.'; $scopeOfChildScopeCtrl2.$digest(); }, false); </script> </body>
ng-repeat
は重い
ng-repeatは使うとすごく気持ちいいし、ul
とか繰り返しのDOMを見つけるとついついng-repeat
を使ってしまう。
が、やっぱり処理的には重いので、静的にHTML書いて済むところはそうするとよし。
directiveの中ではなるべくDOM操作する
<div ng-show=”something”></div> $scope.something = false; $scope.someMethod = function () { $scope.something = true; };
↑みたいにng-show
使うために$scopeに変数持つくらいなら、下記のようにDOM操作で解決する。
var menu = $element.find(‘ul’); menu.hide(); $scope.someMethod = function () { menu.show(); };
自作Directiveの場合は、コンストラクタの引数に$elementが渡されるのでそれを使えよとのこと。
たかがng-show
でもng-repeat
と併用で使われた時に爆発的に監視対象が増える可能性があるので要注意。
まとめ
- 双方向データバインディング系フレームワークは便利だけど内部の動作をある程度知ってないと気付かぬうちにひどいパフォーマンスになりそう。 このあたりの動作の詳しいところはオライリーの本に書いてあるのだろうか。
- 確かEmber.jsは、「この関数はこの値とこの値を更新しますー」みたいなのが定義できて、監視を効率的にできる機能があった気がする。
早くオライリーの本読まないとー