実装に2つのボトルネックが見つかりました。 1つは
ng-repeat
ディレクティブに関連付けられ、もう1つはフィルターに関連付けられました。
この記事では、発生したパフォーマンスの問題を解決または軽減するためのさまざまなアプローチを使用した実験の結果について説明します。 これにより、自分の強みをどこに置くことができ、どのアプローチを使用すべきでないかについてのアイデアとヒントが得られます。
ng-repeatディレクティブが大きなリストでゆっくり動作するのはなぜですか?
2500を超える要素を持つリストに対して双方向バインディングが行われると、
ng-repeat
ディレクティブの動作が遅くなります。 Misko Heveryの投稿でこれについてもっと読むことができます。 これは、AngularJSが「ダーティチェック」メソッドを使用して変更を追跡するためです。 各変更追跡には時間がかかるため、複雑なデータ構造を持つ大きなリストでは、アプリケーションの速度が低下します。
パフォーマンス分析に使用される前提条件
指令の労働時間の追跡:
リストの表示時間を測定するために、
$last
プロパティを使用して
ng-repeat
期間を測定する簡単なディレクティブを作成しました。 基準日は
TimeTracker
サービスに保存されるため、結果はサーバーからのデータのダウンロードに依存しません。
// angular.module('siApp.services').directive('postRepeatDirective', ['$timeout', '$log', 'TimeTracker', function($timeout, $log, TimeTracker) { return function(scope, element, attrs) { if (scope.$last){ $timeout(function(){ var timeFinishedLoadingList = TimeTracker.reviewListLoaded(); var ref = new Date(timeFinishedLoadingList); var end = new Date(); $log.debug("## DOM : " + (end - ref) + " ms"); }); } }; } ]);
HTMLでの使用:
<tr ng-repeat="item in items" post-repeat-directive>…</tr>
Chromeの開発ツールを使用した履歴追跡の機能
Chrome開発者ツールの[年代順]タブ(タイムライン)で、イベント、1秒あたりのブラウザーフレーム数(フレーム)、メモリの割り当て(メモリ)を確認できます。 メモリツールは、メモリリークを検出し、アプリケーションに必要なメモリ量を判断するのに役立ちます。 フレームリフレッシュレートが1秒あたり30フレーム未満の場合、ページのちらつきが問題になります。 フレームツールは、ページのパフォーマンス情報を表示します。 さらに、CPUがjavascriptを消費する時間を表示します。
リストのサイズを制限する基本設定
この問題を軽減する最善の方法は、表示されるリストのサイズを制限することです。 これは、ページネーションまたは無限スクロールによって実行できます。
ページネーション
ページネーション方法は、AngularJS
limitTo
フィルター(バージョン1.1.4以降)と
limitTo
フィルターの組み合わせに基づいています。 このアプローチは、表示されるリストのサイズを制限することにより、表示時間を短縮します。 これは、表示時間を短縮する最も効果的な方法です。
// $scope.currentPage = 0; $scope.pageSize = 75; $scope.setCurrentPage = function(currentPage) { $scope.currentPage = currentPage; } $scope.getNumberAsArray = function (num) { return new Array(num); }; $scope.numberOfPages = function() { return Math.ceil($scope.displayedItemsList.length/ $scope.pageSize); }; // startFrom angular.module('app').filter('startFrom', function() { return function(input, start) { return input.slice(start); };
HTMLでの使用。
<!-- --> <button ng-repeat="i in getNumberAsArray(numberOfPages()) track by $index" ng-click="setCurrentPage($index)">{{$index + 1}}</button <!-- --> <tr ng-repeat="item in displayedItemsList | startFrom: currentPage * pageSize | limitTo:pageSize" /tr>
paginationを使用したくない、または使用できないが、遅いフィルターの問題がまだ心配な場合は、手順5を見るのが面倒にならず、
ng-hide
を使用して不要なリストアイテムを非
ng-hide
してください。
無限のスクロール
私たちのプロジェクトでは、無限スクロールのオプションを考慮しませんでした。 この機能をさらに詳しく知りたい場合は、AngularJSの無限のスクロールプロジェクトにアクセスしてください。
最適化ガイドライン
1.データバインディングなしでリストを表示する
これは、パフォーマンスの問題を引き起こすデータバインディングであるため、最も明らかなソリューションです。 リストを一度表示したいだけで、データを更新したり変更したりする必要がない場合は、データバインディングを削除することは素晴らしいことです。 残念ながら、この場合、データの制御は失われ、私たちには不向きでした。 それが面白い人には、さらにbindonceプロジェクトを見てください。
2.組み込みのメソッド呼び出しを使用してデータを取得しないでください
フィルターされたコレクションを取得するメソッドを使用して、コントローラーでフィルターされたリストを直接取得しないでください。
ng-repeat
は、 $ダイジェストサイクルごとにすべての式を評価します。 これは非常に頻繁に行われます。 この例では、
filteredItems()
はフィルター処理されたコレクションを返します。 実行が遅い場合、アプリケーション全体の速度が急速に低下します。
<li ng-repeat="item in filteredItems()"> <!--, .--> <li ng-repeat="item in items"> <!-- -->
3. 2つのリストを使用します(1つはビューを表示し、もう1つはデータソースとして表示します)
この便利なテンプレートの意味は、データリストから表示リストを分離することです。 これにより、複数のフィルターを事前に適用し、ビューにコレクションキャッシュを適用できます。 次の例は、非常に単純化された実装を示しています。 変数
applyFilter
はコレクションキャッシュを表し、
applyFilter
メソッド
applyFilter
照合を行います。
/* */ // var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}]; // $scope.displayedItems = items; // var filteredLists['active'] = $filter('filter)(items, {"active" : true}); // $scope.applyFilter = function(type) { if (filteredLists.hasOwnProperty(type){ // $scope.displayedItems = filteredLists[type]; } else { /* */ } } // $scope.resetFilter = function() { $scope.displayedItems = items; }
ビューで:
<button ng-click="applyFilter('active')"> </button> <ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>
4. ng-showの代わりにng-ifを使用してテンプレートを補完します
追加のディレクティブまたはテンプレートを使用してリスト項目の追加情報を表示する場合、それをクリックする場合は、 ng-if (バージョン1.1.5以降)を使用します。
ng-if
は表示を無効にします(
ng-show
はあり
ng-if
)。 この場合、追加の要素が追加され、バインディングは必要なときに正確に解決されます。
<li ng-repeat="item in items"> <p> {{ item.title }} </p> <button ng-click="item.showDetails = !item.showDetails">Show details</buttons> <div ng-if="item.showDetails"> {{item.details}} </div> </li>
5. ng-mouseenter、ng-mouseleaveなどのAngularJSディレクティブを使用しないでください。
私たちの意見では、組み込みのAngularJS
ng-mouseenter
と、画面がちらつきます。 ブラウザのフレームレートは、ほとんどの場合、1秒あたり30フレーム未満でした。 純粋なjQueryを使用してアニメーション効果とホバー効果を作成すると、この問題の解決に役立ちます。 後でDOMに追加された要素から通知を受信するには、jQuery.live()でマウスイベントのみをラップすることを忘れないでください。
6.フィルタリングのプロパティを設定します。 ng-showで除外されたアイテムを隠す
長いリストでは、各フィルターが元のリストの独自のサブセットを作成するため、フィルターの動作も遅くなります。 多くの場合、初期データが変更されない場合、フィルターを適用した結果は変わりません。 これを使用するには、データのリストを事前にフィルター処理し、必要なときにフィルター結果を適用して、処理時間を節約できます。
ng-repeat
ディレクティブを使用してフィルターを適用すると、各フィルターは元のコレクションのサブセットを返します。 また、AngularJSは、フィルターによって除外された要素をDOMから削除し、
$destroy
イベントを発生さ
$destroy
ます。これにより、
$scope
からも削除され
$scope
。 入力コレクションが変更されると、フィルターを通過した要素のサブセットも変更されるため、再び要素が再描画または破棄されます。
ほとんどの場合、この動作は正常ですが、ユーザーが頻繁にフィルタリングを使用する場合やリストが非常に大きい場合、要素の継続的なリンクと破棄はパフォーマンスに大きく影響します。 フィルタリングを高速化するには、
ng-show
および
ng-hide
ディレクティブを使用できます。 コントローラーでフィルターを計算し、各要素のプロパティを追加します。 このプロパティの値で
ng-show
を使用します。 この結果、
ng-hide
ディレクティブは、元のコレクション、
$scope
、およびDOMのサブセットから要素を削除する代わりに、特定のクラスを追加するだけです。
-
ng-show
を呼び出す最初の方法は、式の構文を使用することです。ng-show
式は、組み込みのフィルター構文を使用して評価されます。
次のplunkrの例も参照してください。
<input ng-model="query"></input> <li ng-repeat="item in items" ng-show="([item.name] | filter:query).length">{{item.name}}</li>
- 別の方法は、
ng-show
属性を介して特定の値を渡し、渡された値に基づいて別のサブコントローラーでさらに計算することです。 この方法はやや複雑ですが、その適用はよりクリーンです。BenNadelがブログの記事で実証しています 。
7.フィルタリングプロンプトの設定:入力データの転送
節6で説明した方法に加えて、繰り返しフィルタリングの問題を解決する別の方法は、ユーザー入力を送信することです。 たとえば、ユーザーが検索文字列を入力した場合、ユーザーが入力を終了した後にフィルターをアクティブ化する必要があります。
このアプローチを使用する良い例は、次のサービスです。 ビューおよびコントローラーで次のように使用します。
/* */ // 350 . $scope.$watch('queryInput', function(newValue, oldValue) { if (newValue === oldValue) { return; } $debounce(applyQuery, 350); }); var applyQuery = function() { $scope.filter.query = $scope.query; };
/* */ <input ng-model="queryInput"/> <li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>
さらに読むために
- 巨大なアプリケーションのプロジェクト編成
- Misko HeveryのStackOverflowは、Angularのデータバインディングパフォーマンスに関する質問に対する回答です。
- ng-repeatのパフォーマンスを改善するさまざまな方法に関する短い記事
- オンデマンドでビッグデータをダウンロードする
- 良い範囲の記事
- 動的テンプレート用のAngularJSプロジェクト
- データバインディングなしの表示