Angular JS數據的雙向綁定詳解及實例
Angular JS數據的雙向綁定
接觸AngularJS許了,時常問自己一些問題,如果是我實現它,會在哪些方面選擇跟它相同的道路,哪些方面不同。為此,記錄了一些思考,給自己回顧,也供他人參考。
初步大致有以下幾個方面:
- 數據雙向綁定
- 視圖模型的繼承關系
- 模塊和依賴注入的設計
- 待定
數據的雙向綁定
Angular實現了雙向綁定機制。所謂的雙向綁定,無非是從界面的操作能實時反映到數據,數據的變更能實時展現到界面。
一個最簡單的示例就是這樣:
<div ng-controller="CounterCtrl">
<span ng-bind="counter"></span>
<button ng-click="counter=counter+1">increase</button>
</div>
function CounterCtrl($scope) {
$scope.counter = 1;
}
這個例子很簡單,毫無特別之處,每當點擊一次按鈕,界面上的數字就增加一。
綁定數據是怎樣生效的
初學AngularJS的人可能會踩到這樣的坑,假設有一個指令:
var app = angular.module("test", []);
app.directive("myclick", function() {
return function (scope, element, attr) {
element.on("click", function() {
scope.counter++;
});
};
});
app.controller("CounterCtrl", function($scope) {
$scope.counter = 0;
});
<body ng-app="test">
<div ng-controller="CounterCtrl">
<button myclick>increase</button>
<span ng-bind="counter"></span>
</div>
</body>
這個時候,點擊按鈕,界面上的數字并不會增加。很多人會感到迷惑,因為他查看調試器,發(fā)現數據確實已經增加了,Angular不是雙向綁定嗎,為什么數據變化了,界面沒有跟著刷新?
試試在scope.counter++;這句之后加一句scope.digest();再看看是不是好了?
為什么要這么做呢,什么情況下要這么做呢?我們發(fā)現第一個例子中并沒有digest,而且,如果你寫了digest,它還會拋出異常,說正在做其他的digest,這是怎么回事?
我們先想想,假如沒有AngularJS,我們想要自己實現這么個功能,應該怎樣?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>two-way binding</title>
</head>
<body onload="init()">
<button ng-click="inc">
increase 1
</button>
<button ng-click="inc2">
increase 2
</button>
<span style="color:red" ng-bind="counter"></span>
<span style="color:blue" ng-bind="counter"></span>
<span style="color:green" ng-bind="counter"></span>
<script type="text/javascript">
/* 數據模型區(qū)開始 */
var counter = 0;
function inc() {
counter++;
}
function inc2() {
counter+=2;
}
/* 數據模型區(qū)結束 */
/* 綁定關系區(qū)開始 */
function init() {
bind();
}
function bind() {
var list = document.querySelectorAll("[ng-click]");
for (var i=0; i<list.length; i++) {
list[i].onclick = (function(index) {
return function() {
window[list[index].getAttribute("ng-click")]();
apply();
};
})(i);
}
}
function apply() {
var list = document.querySelectorAll("[ng-bind='counter']");
for (var i=0; i<list.length; i++) {
list[i].innerHTML = counter;
}
}
/* 綁定關系區(qū)結束 */
</script>
</body>
</html>
可以看到,在這么一個簡單的例子中,我們做了一些雙向綁定的事情。從兩個按鈕的點擊到數據的變更,這個很好理解,但我們沒有直接使用DOM的onclick方法,而是搞了一個ng-click,然后在bind里面把這個ng-click對應的函數拿出來,綁定到onclick的事件處理函數中。為什么要這樣呢?因為數據雖然變更了,但是還沒有往界面上填充,我們需要在此做一些附加操作。
從另外一個方面看,當數據變更的時候,需要把這個變更應用到界面上,也就是那三個span里。但由于Angular使用的是臟檢測,意味著當改變數據之后,你自己要做一些事情來觸發(fā)臟檢測,然后再應用到這個數據對應的DOM元素上。問題就在于,怎樣觸發(fā)臟檢測?什么時候觸發(fā)?
我們知道,一些基于setter的框架,它可以在給數據設值的時候,對DOM元素上的綁定變量作重新賦值。臟檢測的機制沒有這個階段,它沒有任何途徑在數據變更之后立即得到通知,所以只能在每個事件入口中手動調用apply(),把數據的變更應用到界面上。在真正的Angular實現中,這里先進行臟檢測,確定數據有變化了,然后才對界面設值。
所以,我們在ng-click里面封裝真正的click,最重要的作用是為了在之后追加一次apply(),把數據的變更應用到界面上去。
那么,為什么在ng-click里面調用$digest的話,會報錯呢?因為Angular的設計,同一時間只允許一個$digest運行,而ng-click這種內置指令已經觸發(fā)了$digest,當前的還沒有走完,所以就出錯了。
$digest和$apply
在Angular中,有$apply和$digest兩個函數,我們剛才是通過$digest來讓這個數據應用到界面上。但這個時候,也可以不用$digest,而是使用$apply,效果是一樣的,那么,它們的差異是什么呢?
最直接的差異是,$apply可以帶參數,它可以接受一個函數,然后在應用數據之后,調用這個函數。所以,一般在集成非Angular框架的代碼時,可以把代碼寫在這個里面調用。
var app = angular.module("test", []);
app.directive("myclick", function() {
return function (scope, element, attr) {
element.on("click", function() {
scope.counter++;
scope.$apply(function() {
scope.counter++;
});
});
};
});
app.controller("CounterCtrl", function($scope) {
$scope.counter = 0;
});
除此之外,還有別的區(qū)別嗎?
在簡單的數據模型中,這兩者沒有本質差別,但是當有層次結構的時候,就不一樣了??紤]到有兩層作用域,我們可以在父作用域上調用這兩個函數,也可以在子作用域上調用,這個時候就能看到差別了。
對于$digest來說,在父作用域和子作用域上調用是有差別的,但是,對于$apply來說,這兩者一樣。我們來構造一個特殊的示例:
var app = angular.module("test", []);
app.directive("increasea", function() {
return function (scope, element, attr) {
element.on("click", function() {
scope.a++;
scope.$digest();
});
};
});
app.directive("increaseb", function() {
return function (scope, element, attr) {
element.on("click", function() {
scope.b++;
scope.$digest(); //這個換成$apply即可
});
};
});
app.controller("OuterCtrl", ["$scope", function($scope) {
$scope.a = 1;
$scope.$watch("a", function(newVal) {
console.log("a:" + newVal);
});
$scope.$on("test", function(evt) {
$scope.a++;
});
}]);
app.controller("InnerCtrl", ["$scope", function($scope) {
$scope.b = 2;
$scope.$watch("b", function(newVal) {
console.log("b:" + newVal);
$scope.$emit("test", newVal);
});
}]);
<div ng-app="test">
<div ng-controller="OuterCtrl">
<div ng-controller="InnerCtrl">
<button increaseb>increase b</button>
<span ng-bind="b"></span>
</div>
<button increasea>increase a</button>
<span ng-bind="a"></span>
</div>
</div>
這時候,我們就能看出差別了,在increase b按鈕上點擊,這時候,a跟b的值其實都已經變化了,但是界面上的a沒有更新,直到點擊一次increase a,這時候剛才對a的累加才會一次更新上來。怎么解決這個問題呢?只需在increaseb這個指令的實現中,把$digest換成$apply即可。
當調用$digest的時候,只觸發(fā)當前作用域和它的子作用域上的監(jiān)控,但是當調用$apply的時候,會觸發(fā)作用域樹上的所有監(jiān)控。
因此,從性能上講,如果能確定自己作的這個數據變更所造成的影響范圍,應當盡量調用$digest,只有當無法精確知道數據變更造成的影響范圍時,才去用$apply,很暴力地遍歷整個作用域樹,調用其中所有的監(jiān)控。
從另外一個角度,我們也可以看到,為什么調用外部框架的時候,是推薦放在$apply中,因為只有這個地方才是對所有數據變更都應用的地方,如果用$digest,有可能臨時丟失數據變更。
臟檢測的利弊
很多人對Angular的臟檢測機制感到不屑,推崇基于setter,getter的觀測機制,在我看來,這只是同一個事情的不同實現方式,并沒有誰完全勝過誰,兩者是各有優(yōu)劣的。
大家都知道,在循環(huán)中批量添加DOM元素的時候,會推薦使用DocumentFragment,為什么呢,因為如果每次都對DOM產生變更,它都要修改DOM樹的結構,性能影響大,如果我們能先在文檔碎片中把DOM結構創(chuàng)建好,然后整體添加到主文檔中,這個DOM樹的變更就會一次完成,性能會提高很多。
同理,在Angular框架里,考慮到這樣的場景:
function TestCtrl($scope) {
$scope.numOfCheckedItems = 0;
var list = [];
for (var i=0; i<10000; i++) {
list.push({
index: i,
checked: false
});
}
$scope.list = list;
$scope.toggleChecked = function(flag) {
for (var i=0; i<list.length; i++) {
list[i].checked = flag;
$scope.numOfCheckedItems++;
}
};
}
如果界面上某個文本綁定這個numOfCheckedItems,會怎樣?在臟檢測的機制下,這個過程毫無壓力,一次做完所有數據變更,然后整體應用到界面上。這時候,基于setter的機制就慘了,除非它也是像Angular這樣把批量操作延時到一次更新,否則性能會更低。
所以說,兩種不同的監(jiān)控方式,各有其優(yōu)缺點,最好的辦法是了解各自使用方式的差異,考慮出它們性能的差異所在,在不同的業(yè)務場景中,避開最容易造成性能瓶頸的用法。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關文章
Angular?Tree?Shaking優(yōu)化機制原理詳解
這篇文章主要為大家介紹了Angular?Tree?Shaking優(yōu)化機制原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
Angular中封裝fancyBox(圖片預覽)遇到問題小結
這篇文章主要介紹了Angular中封裝fancyBox(圖片預覽)遇到的問題小結,需要的朋友可以參考下2017-09-09
Angular.js實現多個checkbox只能選擇一個的方法示例
這篇文章主要給大家介紹了利用Angular.js實現多個checkbox只能選擇一個的方法,文中給出了詳細的示例代碼,相信對大家具有一定的參考價值,下面來一起看看吧。2017-02-02
AngularJS中directive指令使用之事件綁定與指令交互用法示例
這篇文章主要介紹了AngularJS中directive指令使用之事件綁定與指令交互用法,結合實例形式分析了directive指令在模板的使用,事件的綁定及元素、屬性、控制器之間的交互相關操作技巧,需要的朋友可以參考下2016-11-11

