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呼叫,以上每一個動作都會引發重算。
用實例來驗證這點:
<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8">
<title>Filter執行頻率示範</title>
<style>
input,div { display: block; margin: 12px; }
</style>
</head>
<bodyng-controller="ctrl as m">
<inputng-model="m.text"/>
<inputng-model="m.other"/>
<inputtype="button"value="Do Nothing"
ng-click="m.doNothing()"/>
<div>
by filter: {{m.text|sayHi}}
</div>
<div>
by $watch: {{m.message}}
</div>
<scriptsrc="//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() {
returnfunction(value) {
console.log("filter-exec");
return"Hi, " + value;
}
})
</script>
</body>
</html>
在範例中,text為目標屬性,用以保存姓名,最終目標要顯示"Hi, " + text。程式示範了兩種不同做法:
- 宣告sayHi Filter,透過{{ m.text | sayHi }}轉換成"Hi, " + text
- 多宣告一個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系列]