blog

AngularJSのパフォーマンスチューニングのための7つの提案

AnglarJSは優れたウェブフレームワークとして、フロントエンド開発の負担を大幅に軽減することができます。最近、Sebastian Fröstlはブログ記事「AngularJS Tunin...

Jun 19, 2014 · 9 min. read
シェア

AnglarJSは素晴らしいですが、複雑なデータ構造を含む大きなリストを扱う場合、動作が非常に遅くなります。これはコアの管理ページをAngularJSに移行する際に遭遇した問題でした。これらのページは500行のデータを表示するときにスムーズに動作するはずでしたが、最初の方法ではレンダリングに7秒という恐ろしい時間がかかりました。

その後、実装において2つの大きなパフォーマンスの問題が確認されました。ひとつは "ng-repeat "ディレクティブ、もうひとつはフィルターです。

次の記事では、さまざまなアプローチでパフォーマンス問題を解決した経験をご紹介します。

まず、なぜAngularJSのng-repeatは大きなリストを扱うと遅くなるのでしょうか?

AngularJSのng-repeatは、2500以上の双方向データバインディングを処理すると遅くなります。これは、AngularJSが「ダーティチェック」機能によって変更を検出するためです。各チェックには時間がかかるため、複雑なデータ構造を持つ大きなリストはアプリケーションの速度を低下させます。

II.パフォーマンス向上のための前提条件

タイムレコーダー

リストのレンダリングにかかる時間を計測するために、"ng-repeat" プロパティ "$last" を使って時間を記録する簡単なプログラムを書きました。時間はTimeTrackerサービスに保存されるので、時間の記録はサーバーサイドのデータ読み込みとは別に行われます。

// Post repeat directive for logging the rendering time   
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 rendering list took: " + (end - ref) + " ms");   
         });   
       }   
    };   
  }   
]);   
    
// Use in HTML:   
<tr ng-repeat="item in items" post-repeat-directive> </tr>   

Chromeデベロッパーツールのタイムラインプロパティ

Chrome 開発者ツールの[タイムライン]タブでは、イベント、ブラウザの 1 秒あたりのフレーム数、メモリ割り当てを確認できます。メモリ」ツールは、メモリ リークやページに必要なメモリ量を検出するために使用します。フレーム レートが 30 フレーム/秒を下回ると、ちらつきが発生します。フレーム」ツールは、レンダリングパフォーマンスを理解するのに役立ち、JavaScriptタスクに費やされたCPU時間の量も表示します。

III.リストサイズの制限による基本チューニング

この問題を軽減する最善の方法は、表示されるリストのサイズを制限することです。これはページングや無限スクロールバーを追加することで実現できます。

タブウィンドウ

ページングには、AngularJSの "limitTo "フィルタと "startFrom "フィルタを使用できます。表示するリストのサイズを制限することで、レンダリング時間を短縮できます。これはレンダリング時間を短縮する最も効率的な方法です。

// Pagination in controller   
$scope.currentPage = 0;   
$scope.pageSize = 75;   
$scope.numberOfPages = function() {   
    return Math.ceil($scope.displayedItemsList.length/ $scope.pageSize);   
};   
    
// Start from filter   
angular.module('app').filter('startFrom', function() {   
    return function(input, start) {           
        return input.slice(start);   
};   
    
// Use in HTML   
// Pagination buttons   
<button ng-repeat="i in getNumber(numberOfPages()) track by $index" ng-click="setCurrentPage($index)">{{$index + 1}}</button   
    
// Displayed list   
<tr ng-repeat="item in displayedItemsList | startFrom: currentPage * pageSize  | limitTo:pageSize" /tr>   

ページングを使用できない/使用したくないが、フィルタリング処理が遅い場合は、必ず最初の5つのステップをチェックし、"ng-show "を使用して余分なリスト要素を非表示にしてください。

無限スクロールバー

IV.最適化の7つの法則

1.データバインディングなしのリストのレンダリング

データ・バインディングがパフォーマンス問題の原因である可能性が最も高いので、これは最も明白な解決策です。リストを一度だけ表示したいだけで、データを更新したり変更したりする必要がない場合は、データバインディングを削除するのが最適です。残念ながらデータの制御はできなくなりますが、この方法に代わるものはありません。詳細はこちら: https://.//ce.

2.インラインメソッドを使用してデータを計算しないでください。

コントローラで直接リストをフィルタリングするために、フィルタリンクを取得できるメソッドを使用しないでください。"ng-repeat "は全ての[$digest(http://...//ng.$.pe#$st)%5D式を評価します。この場合、"ms() "はフィルター・リンクを返します。評価プロセスが遅いと、アプリケーション全体がすぐに遅くなってしまいます。

<li ng-repeat="item in filteredItems()">//これは、頻繁に評価しなければならないので、良い方法ではない。   
<li ng-repeat="item in items">//これが使用する方法である   

3.2つのリストを使用

表示するリストと全体のデータリストを分離することは、非常に便利なモデルです。いくつかのフィルタを前処理して、キャッシュに保存されたリンクをビューに適用することができます。filteredLists 変数がキャッシュ内のリンクを保持し、 applyFilter メソッドがマッピングを処理します。

/* Controller */   
// Basic list   
var items = [{name:"John", active:true }, {name:"Adam"}, {name:"Chris"}, {name:"Heather"}];   
    
// Init displayedList   
$scope.displayedItems = items;   
    
// Filter Cache   
var filteredLists['active'] = $filter('filter)(items, {"active" : true});   
    
// Apply the filter   
$scope.applyFilter = function(type) {   
    if (filteredLists.hasOwnProperty(type){ // Check if filter is cached   
        $scope.displayedItems = filteredLists[type];   
    } else {   
        /* Non cached filtering */   
    }   
}   
    
// Reset filter   
$scope.resetFilter = function() {   
    $scope.displayedItems = items;   
}   
    
/* View */   
<button ng-click="applyFilter('active')">Select active</button>   
<ul><li ng-repeat="item in displayedItems">{{item.name}}<li></ul>   

#p#

4. 他のテンプレートでは、ng-showの代わりにng-ifを使用します。

ディレクティブやテンプレートを使用して、リスト項目をクリックしたときの詳細などの追加情報をレンダリングする場合は、必ず ng-if使用してください。そのため、必要に応じて他の DOM やデータバインディングを評価することができます。

<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などのディレクティブを使用しないでください。

ng-mouseenterのような内部ディレクティブを使用すると、AngularJSはページをちらつかせます。ブラウザのフレームレートは通常30フレーム/秒より低いです。jQueryを使用してアニメーションするマウスホバーエフェクトを作成すると、問題を解決できます。マウスイベントをjQueryの.live()関数に入れるようにしてください。

6.フィルタリングのヒント: ng-showで冗長な要素を隠す

長いリストの場合、フィルタを使用すると元のリストへのサブリンクが作成されるため、同様に効率が悪くなります。多くの場合、データは変更されず、フィルタリングされた結果は同じままです。そのため、データのリストに事前にフィルタをかけ、適宜ビューに適用することで、処理時間を大幅に短縮できます。

ng-repeatディレクティブでフィルタを使用し、それぞれが元のリンクのサブセットを返します。AngularJSはDOMから冗長な要素を削除し、$scopeからも削除します。フィルタの入力が変更されると、サブセットも変更され、要素を再リンクするか $destroy を再度呼び出す必要があります。

たいていの場合はこれでよいのですが、ユーザーが大量にフィルタリングを行う場合、あるいはリストがとても大きい場合は、リンクと破棄を繰り返すとパフォーマンスに影響します。フィルタリングを高速化するには、ng-showディレクティブとng-hideディレクティブを使用します。コントローラでフィルタリングを行い、各項目に属性を追加します。ng-showをトリガーするためにその属性に依存すると、サブリスト、$scope、DOMからそれらを削除する代わりに、それらの要素にのみng-hideクラスを追加することになります。

ng-showをトリガーする1つの方法は、ng-showの値が式構文によって決定される式構文を使用することです。次の例を参照してください:

<input ng-model="query"></input>   
<li ng-repeat="item in items" ng-show="([item.name] | filter:query).length">{{item.name}}
</li><span style="font-size: 14px; line-height: 24px; font-family: Helvetica, Tahoma, Arial, sans-serif; white-space: normal;"></span>   

別の方法として、ng-show にプロパティを渡し、別のサブコントローラで計算を行う方法もあります。この方法は少し複雑ですが、より明示的な方法です。

7.フィルタリングのヒント:アンチディザリング入力

ポイント6で示した連続的なフィルタリングの問題を解決するもう一つの方法は、ユーザー入力の揺れを防ぐことです。例えば、ユーザーが検索語を入力した場合、ユーザーが入力を止めた後にのみフィルターが有効になります。このアンチジッタリングサービスを使用した良い解決策は、/Kd/にあります。これをビューやコントローラに適用したものを以下に示します。

/* Controller */   
// Watch the queryInput and debounce the filtering by 350 ms.   
$scope.$watch('queryInput', function(newValue, oldValue) {   
    if (newValue === oldValue) { return; }   
    $debounce(applyQuery, 350);   
});   
var applyQuery = function() {   
    $scope.filter.query = $scope.query;   
};   
    
/* View */   
<input ng-model="queryInput"/>   
<li ng-repeat= item in items | filter:filter.query>{{ item.title }} </li>   
Read next

スパニングツリー(STP)選出プロセス

スパニングツリープロトコルには、レイヤ2スイッチネットワークにおけるパスループの回避と、レイヤ2スイッチネットワークにおける冗長バックアップの実現という2つの重要な役割があります。この記事では、スパニングツリープロトコルの設定における選出プロセスについて詳しく説明します。

Jun 19, 2014 · 3 min read