Angular不需宣告observable就能實現屬性連動,背後靠的是Dirty Check機制的反覆比對,代價是產生許多無謂計算(延伸閱讀:保安,可以讓Angular這樣算了又算算了又算嗎),而好用的Filter特性也在無謂重算之列。當我們寫"{{ propA | convFormat }}",當NG要檢查「是否有任何數值發生變化」時,就得將propA傳給convFormat重算結果。Dirty Check重算檢查的頻率或許比大家想像得多,propB、propC或procD屬性改變、ng-click/ng-blur/ng-keydown/ng-mousedown等事件被觸發,或是$http完成AJAX呼叫,以上每一個動作都會引發重算。

用實例來驗證這點:

<!DOCTYPE html>
<html ng-app="app">
<head>
 
  <meta charset="utf-8">
  <title>Filter執行頻率示範</title>
  <style>
    input,div { display: block; margin: 12px; }
  </style>
</head>
<body ng-controller="ctrl as m">
  <input ng-model="m.text" />
  <input ng-model="m.other" /> 
  <input type="button" value="Do Nothing"
         ng-click="m.doNothing()" />
  <div>
    by filter: {{m.text|sayHi}}
  </div>
  <div>
    by $watch: {{m.message}}
  </div>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
  <script>
    function MyCtrl($scope) {
      var self = this;
      self.text = "Jeffrey";
      self.other = "blah";
      self.doNothing = function() { };
      self.message = "";
      $scope.$watch(
        function() {
          return self.text; 
        }, 
        function(newValue) {
          console.log("watch-changed")
          self.message = "Hi, " + newValue;
        });
    }
    angular.module("app", [])
    .controller("ctrl", MyCtrl)
    .filter("sayHi", function() {
      return function(value) {
        console.log("filter-exec");
        return "Hi, " + value;
      }
    })
  </script>
</body>
</html>

Demo

在範例中,text為目標屬性,用以保存姓名,最終目標要顯示"Hi, " + text。程式示範了兩種不同做法:

  1. 宣告sayHi Filter,透過{{ m.text | sayHi }}轉換成"Hi, " + text
  2. 多宣告一個message屬性,使用$wath()追蹤text,text異動時更新message為"Hi, " + text

我們分別在sayHi Filter及$watch()中加入console.log(),藉以觀察二者被執行次數。另外再抓兩個路人來當臨演:other屬性以及ng-click動作doNothing()。方法2較囉嗦,但優點是它只有在text變動時才執行字串相加邏輯。而sayFilter除了text變更後跑兩次(一次被text更新觸發,另一次源自Dirty Check重算),在other改變及ng-click時也會重跑。如以下操作展示:

字串計算耗用資源有限,多算幾次差別不大,但若換成更複雜的運算或是更高頻的Dirty Check環境,就足以形成明顯效能差異。

針對這點,Angular 1.3做了一些改良,讓Filter變得更聰明!從1.3起,Filter會Cache計算結果,若來源資料不變就直接使用Cache內容,省去無謂重算。沿用前述程式,只將Angular由1.2.26換為1.3.0(2015年1月已到1.3.2版,但此處用1.3.0證明變化從1.3開始),實測可發現結果明顯不同:Demo

sayHi Filter函式執行時機變得跟$watch()一致!只有在text改變時執行,不受other屬性及ng-click事件影響,Angular 1.3+的Filter真的變聰明了!

【結論】從Angular 1.3起,我們可以更放心大膽使用Filter,不必再為了效能刻意改寫成$watch囉~
(註:若此一行為改變造成舊程式崩壞,可在Filter中設定$stateful參數,恢復成1.2.x的行為模式)

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

Comments

Be the first to post a comment

Post a comment