AngularJS
プロファイリングとパフォーマンス
サーチ…
7つの簡単なパフォーマンスの改善
1)ng-repeatを慎重に使用する
ビューでng-repeat
を使用すると、特にネストされたng-repeat
がある場合、パフォーマンスが低下します。
これは超スローです!
<div ng-repeat="user in userCollection">
<div ng-repeat="details in user">
{{details}}
</div>
</div>
ネストされたリピートを可能な限り避けるようにしてください。 ng-repeat
パフォーマンスを向上させる1つの方法はtrack by $index
(または他のidフィールド)でtrack by $index
を使用することです。デフォルトでは、 ng-repeat
はオブジェクト全体ng-repeat
追跡します。 track by
、Angularはオブジェクトを$index
またはobject idだけ見る。
<div ng-repeat="user in userCollection track by $index">
{{user.data}}
</div>
ページネーション 、 バーチャルスクロール 、 無限スクロール 、 limitToなどの他のアプローチを使用してください 。可能であれば、大規模なコレクションの反復処理を避けるようにしてください。
2)一度バインドする
Angularは双方向データバインディングを持っています。それはあまりにも多く使用すると遅くなるコストが付属しています。
遅いパフォーマンス
<!-- Default data binding has a performance cost -->
<div>{{ my.data }}</div>
より速いパフォーマンス (AngularJS> = 1.3)
<!-- Bind once is much faster -->
<div>{{ ::my.data }}</div>
<div ng-bind="::my.data"></div>
<!-- Use single binding notation in ng-repeat where only list display is needed -->
<div ng-repeat="user in ::userCollection">
{{::user.data}}
</div>
「一度バインド」表記を使用すると、最初の一連のダイジェストサイクル後に値が安定するのを待つように、Angularに指示します。 Angularはその値をDOMで使用し、すべてのウォッチャーを削除して静的な値にし、もはやモデルにバインドされなくなります。
{{}}
ははるかに遅いです。
このng-bind
はディレクティブであり、渡された変数にウォッチャーを配置します。したがって、渡された値が実際に変更された場合にのみ、 ng-bind
が適用されます。
一方、大括弧は、必要でない場合でも、すべての$digest
で汚れチェックとリフレッシュされます。
3)スコープ関数とフィルタに時間がかかる
AngularJSにはダイジェストループがあります。ダイジェスト・サイクルが実行されるたびに、すべての関数がビュー内にあり、フィルターが実行されます。ダイジェストループは、モデルが更新されたときに実行され、アプリケーションが遅くなる可能性があります(ページが読み込まれる前にフィルタが複数回ヒットする可能性があります)。
これを回避する:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
より良いアプローチ
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
コントローラーができる場所:
app.controller('bigCalulations', function(valueService) {
// bad, because this is called in every digest loop
this.calculateMe = function() {
var t = 0;
for(i = 0; i < 1000; i++) {
t += i;
}
return t;
}
// good, because this is executed just once and logic is separated in service to keep the controller light
this.preCalulatedValue = valueService.valueCalculation(); // returns 499500
});
4人のウォッチャー
ウォッチャーはパフォーマンスを大幅に落とします。ウォッチャーが増えるほどダイジェストループが長くなり、UIが遅くなります。ウォッチャーが変更を検出すると、ダイジェストループが開始され、ビューが再描画されます。
Angularの可変的な変更を手動で監視する方法は3つあります。
$watch()
- 値の変化を監視する
$watchCollection()
- コレクションの変更を監視します(通常の$watch
以上を$watch
)
$watch(..., true)
- これをできるだけ避け 、 "深い腕時計"を実行し、パフォーマンスをwatchCollection
ます( watchCollection
以上を監視します)
新しい時計を作成するビューで変数をバインドする場合は、 {{::variable}}
を使用して、特にループ内で時計を作成しないようにしてください。
その結果、使用しているウォッチャーの数を追跡する必要があります。このスクリプトでウォッチャーをカウントすることができます(クレジットに@Words同様ジャレド ウォッチャーの数 )
(function() {
var root = angular.element(document.getElementsByTagName('body')),
watchers = [],
f = function(element) {
angular.forEach(['$scope', '$isolateScope'], function(scopeProperty) {
if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
watchers.push(watcher);
});
}
});
angular.forEach(element.children(), function(childElement) {
f(angular.element(childElement));
});
};
f(root);
// Remove duplicate watchers
var watchersWithoutDuplicates = [];
angular.forEach(watchers, function(item) {
if(watchersWithoutDuplicates.indexOf(item) < 0) {
watchersWithoutDuplicates.push(item);
}
});
console.log(watchersWithoutDuplicates.length);
})();
5)ng-if / ng-show
これらの機能は動作が非常に似ています。 ng-if
はDOMから要素を削除しますが、 ng-show
は要素を隠すだけですが、すべてのハンドラを保持します。表示したくないコードの部分がある場合は、 ng-if
使用してください。
それは使用のタイプに依存しますが、しばしば一方が他方よりも適しています。
要素が必要でない場合は、
ng-if
オン/オフをすばやく切り替えるには、
ng-show/ng-hide
使用します<div ng-repeat="user in userCollection"> <p ng-if="user.hasTreeLegs">I am special<!-- some complicated DOM --></p> <p ng-show="user.hasSubscribed">I am awesome<!-- switch this setting on and off --></p> </div>
疑問があれば、 ng-if
を使ってテストしてください!
6)デバッグを無効にする
デフォルトでは、バインド・ディレクティブとスコープは、コードに余分なクラスとマークアップを残し、さまざまなデバッグ・ツールを支援します。このオプションを無効にすると、ダイジェストサイクル中にこれらのさまざまな要素をレンダリングしなくなります。
angular.module('exampleApp', []).config(['$compileProvider', function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}]);
7)依存性注入を使用してリソースを公開する
Dependency Injectionは、オブジェクトがオブジェクトを作成するのではなく、その依存関係が与えられているソフトウェア設計パターンです。ハードコーディングされた依存関係を削除し、必要に応じて変更することができます。
すべての注入可能な関数のこのような文字列解析に関連するパフォーマンスのコストについては疑問に思うかもしれません。 Angularは、初めて$ injectプロパティをキャッシュすることでこれを処理します。したがって、関数が呼び出される必要があるたびにこれは発生しません。
PROヒント:最高のパフォーマンスでアプローチを探している場合は、$ injectプロパティアノテーションアプローチを使用してください。このアプローチは、このロジックがアノテート関数の次のチェック内にラップされるため、関数定義の解析を完全に回避します。if(!($ inject = fn。$ inject))。 $ injectがすでに利用可能であれば、解析は不要です!
var app = angular.module('DemoApp', []);
var DemoController = function (s, h) {
h.get('https://api.github.com/users/angular/repos').success(function (repos) {
s.repos = repos;
});
}
// $inject property annotation
DemoController['$inject'] = ['$scope', '$http'];
app.controller('DemoController', DemoController);
PROヒント2: ng-app
と同じ要素にng-strict-di
ディレクティブを追加して厳密なDIモードを選択することができます。これは、サービスが暗黙の注釈を使用しようとするたびにエラーを送出します。例:
<html ng-app="DemoApp" ng-strict-di>
または、手動ブートストラップを使用する場合:
angular.bootstrap(document, ['DemoApp'], {
strictDi: true
});
束縛
Angularは、素晴らしい双方向データバインディングを持つことで高い評価を得ています。デフォルトでは、モデルまたはビューコンポーネントのデータが変更されるたびに、モデルコンポーネントとビューコンポーネントの間でバインドされた値が継続的に同期されます。
これはあまりにも多く使用すると少し遅くなるというコストが伴います。これにより、パフォーマンスが大幅に向上します。
パフォーマンスが悪い: {{my.data}}
1回限りのバインディングを使用するには、変数名の前に2つのコロン::
を追加します。この場合、値はmy.dataが定義されると更新されます。データの変更を監視しないように明示しています。 Angularは値のチェックを実行しないため、各ダイジェストサイクルで評価される式が少なくなります。
1回限りのバインディングを使用した優れたパフォーマンスの例
{{::my.data}}
<span ng-bind="::my.data"></span>
<span ng-if="::my.data"></span>
<span ng-repeat="item in ::my.data">{{item}}</span>
<span ng-class="::{ 'my-class': my.data }"></div>
注意:これにより、 my.data
の双方向データバインディングが削除されるため、アプリケーション内でこのフィールドが変更された場合でも、そのフィールドは自動的にビューに反映されません。したがって、アプリケーションのライフサイクル全体にわたって変化しない値に対してのみ使用してください 。
スコープ関数とフィルタ
AngularJSにはダイジェスト・ループがあり、ダイジェスト・サイクルが実行されるたびにビュー内のすべての機能が実行されます。ダイジェストループは、モデルが更新されたときに実行され、アプリケーションが遅くなる可能性があります(ページがロードされる前にフィルタが複数回ヒットする可能性があります)。
あなたはこれを避けるべきです:
<div ng-controller="bigCalulations as calc">
<p>{{calc.calculateMe()}}</p>
<p>{{calc.data | heavyFilter}}</p>
</div>
より良いアプローチ
<div ng-controller="bigCalulations as calc">
<p>{{calc.preCalculatedValue}}</p>
<p>{{calc.data | lightFilter}}</p>
</div>
コントローラーのサンプルは次のとおりです。
.controller("bigCalulations", function(valueService) {
// bad, because this is called in every digest loop
this.calculateMe = function() {
var t = 0;
for(i = 0; i < 1000; i++) {
t = t + i;
}
return t;
}
//good, because it is executed just once and logic is separated in service to keep the controller light
this.preCalulatedValue = valueService.caluclateSumm(); // returns 499500
});
ウォッチャー
ウォッチャは何らかの値を監視し、この値が変更されたことを検出する必要がありました。
$watch()
または$watchCollection
呼び出した後、新しいウォッチャーは現在のスコープの内部ウォッチャーコレクションに追加します。
では、ウォッチャーとは何ですか?
Watcherは簡単な関数で、ダイジェストサイクルごとに呼び出され、値を返します。 Angularは、前の呼び出しと同じでない場合は戻り値をチェックします。関数$watch()
または$watchCollection
に2番目のパラメータで渡されたコールバックが実行されます。
(function() {
angular.module("app", []).controller("ctrl", function($scope) {
$scope.value = 10;
$scope.$watch(
function() { return $scope.value; },
function() { console.log("value changed"); }
);
}
})();
ウォッチャーはパフォーマンスのキラーです。あなたが持つウォッチャーが多いほど、ダイジェストループを作るのに時間がかかり、UIが遅くなります。ウォッチャーが変更を検出すると、ダイジェストループが開始されます(すべての画面で再計算されます)
角度の変化を手動で監視する方法は3つあります。
$watch()
- 値の変更だけを監視する
$watchCollection()
- コレクションの変更を監視します(通常の$ watch以上を監視します)
$watch(..., true)
- 可能な限り避け 、 "深い腕時計"を実行し、パフォーマンスを犠牲にします(watchCollection以上を監視します)
ビュー内で変数をバインドする場合は、新しいウォッチャーを作成していることに注意してください。特に{{::variable}}
を使用してウォッチャーを作成しないでください
その結果、使用しているウォッチャーの数を追跡する必要があります。あなたはこのスクリプトでウォッチャーを数えることができます( Jaredのように@Wordsのように - ページ上のウォッチの総数を数える方法?
(function() {
var root = angular.element(document.getElementsByTagName("body")),
watchers = [];
var f = function(element) {
angular.forEach(["$scope", "$isolateScope"], function(scopeProperty) {
if(element.data() && element.data().hasOwnProperty(scopeProperty)) {
angular.forEach(element.data()[scopeProperty].$$watchers, function(watcher) {
watchers.push(watcher);
});
}
});
angular.forEach(element.children(), function(childElement) {
f(angular.element(childElement));
});
};
f(root);
// Remove duplicate watchers
var watchersWithoutDuplicates = [];
angular.forEach(watchers, function(item) {
if(watchersWithoutDuplicates.indexOf(item) < 0) {
watchersWithoutDuplicates.push(item);
}
});
console.log(watchersWithoutDuplicates.length);
})();
独自のスクリプトを作成したくない場合は、 ng-statsというオープンソースユーティリティがあります。このユーティリティはページに埋め込まれたリアルタイムチャートを使用して、Angularが管理している時計の数と時間とともにダイジェストサイクルの頻度および持続時間。このユーティリティはshowAngularStats
というグローバル関数を公開しています。この関数を呼び出すと、チャートの動作方法を設定できます。
showAngularStats({
"position": "topleft",
"digestTimeThreshold": 16,
"autoload": true,
"logDigest": true,
"logWatches": true
});
上のサンプルコードは、自動的にページに次のグラフを表示します( インタラクティブなデモ )。
ng-if対ng-show
これらの機能は動作が非常に似ています。違いは、 ng-if
がDOMから要素を削除することです。表示されないコードの大部分があるng-if
は、 ng-if
が行く方法です。 ng-show
は要素を隠すだけですが、すべてのハンドラを保持します。
ng-if
ngIfディレクティブは、式に基づいてDOMツリーの一部を削除または再作成します。 ngIfに割り当てられた式が偽の値に評価された場合、要素はDOMから削除されます。そうでない場合、要素のクローンがDOMに再挿入されます。
ng-show
ngShowディレクティブは、ngShow属性に指定された式に基づいて、指定されたHTML要素を表示または非表示にします。要素を表示または非表示にするには、ng-hide CSSクラスを削除するか、要素に追加します。
例
<div ng-repeat="user in userCollection">
<p ng-if="user.hasTreeLegs">I am special
<!-- some complicated DOM -->
</p>
<p ng-show="user.hasSubscribed">I am aweosme
<!-- switch this setting on and off -->
</p>
</div>
結論
それは使用のタイプに依存しますが、しばしば一方が他方よりも適切です(例えば、エレメントが必要でない時間の95%がng-if
使用するng-if
、DOMエレメントの可視性を切り替える必要がある場合は、 ng-show
)。
疑問がある場合は、 ng-if
を使用してテストしてください!
注 : ng-if
は新しい分離スコープを作成しますが、 ng-show
およびng-hide
は作成しません。親スコーププロパティに直接アクセスできない場合は、 $parent.property
使用します。
あなたのモデルを討論する
<div ng-controller="ExampleController">
<form name="userForm">
Name:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ debounce: 1000 }" />
<button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
</form>
<pre>user.name = </pre>
</div>
上記の例では、1秒間の1000ミリ秒のデバウンス値を設定しています。これはかなり遅れていますが、多くの$digest
サイクルでng-model
が繰り返し入力されるのを防ぐことができます。
入力フィールドやその他の場所で即時更新が不要な場所でデバウンスを使用すると、Angularアプリケーションのパフォーマンスを大幅に向上させることができます。時間をずらすことができるだけでなく、アクションがトリガーされたときに遅延することもできます。すべてのキーストロークでモデルを更新したくない場合は、ぼかしも更新することができます。
現在のスコープ以外のスコープに登録されているリスナーは、常に登録抹消する
以下に示すように、現在のスコープ以外のスコープを登録解除する必要があります。
//always deregister these
$rootScope.$on(...);
$scope.$parent.$on(...);
あなたは角度がそれを処理するので、現在のスコープでリスナーを登録解除する必要はありません:
//no need to deregister this
$scope.$on(...);
リスナーの$rootScope.$on
、別のコントローラーに移動するとメモリに残ります。これにより、コントローラが有効範囲外になった場合にメモリリークが発生します。
しないでください
angular.module('app').controller('badExampleController', badExample);
badExample.$inject = ['$scope', '$rootScope'];
function badExample($scope, $rootScope) {
$rootScope.$on('post:created', function postCreated(event, data) {});
}
行う
angular.module('app').controller('goodExampleController', goodExample);
goodExample.$inject = ['$scope', '$rootScope'];
function goodExample($scope, $rootScope) {
var deregister = $rootScope.$on('post:created', function postCreated(event, data) {});
$scope.$on('$destroy', function destroyScope() {
deregister();
});
}