瀏覽器提供有User Event
觸發事件的API
,例如,click
,change
等javascript
瀏覽器沒有數據監測API
。 AngularJS
提供了 $apply()
,$digest()
,$watch()
。css
{{}}
Object.defineProperty()
中使用 setter
/ getter
鉤子實現。html
[()]
事件綁定加上屬性綁定構成雙向綁定。java
你們先看運行效果,運行後,點增長,數字會+1,點減小,數字會-1,就是這麼一個簡單的頁面,視圖到底爲什麼會自動更新數據呢?瀏覽器
我先把最粗糙的源碼放出來,你們先看看,有看不懂得地方再議。app
老規矩,初始化頁面框架
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="./test_01.js" charset="utf-8"></script>
<title>手寫髒檢查</title>
<style type="text/css"> button { height: 60px; width: 100px; } p { margin-left: 20px; } </style>
</head>
<body>
<div>
<button type="button" ng-click="increase">增長</button>
<button type="button" ng-click="decrease">減小</button> 數量:
<span ng-bind="data"></span>
</div>
<br>
<!-- 合計 = <span ng-bind="sum"></span> -->
</body>
</html>
複製代碼
下面是JS
源碼:1.0
版本ui
window.onload = function() {
'use strict';
var scope = { // 至關於$scope
"increase": function() {
this.data++;
},
"decrease": function() {
this.data--;
},
data: 0
}
function bind() {
var list = document.querySelectorAll('[ng-click]');
for (var i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function(index) {
return function() {
var func = this.getAttribute('ng-click');
scope[func](scope);
apply();
}
})(i)
}
}
function apply() {
var list = document.querySelectorAll('[ng-bind]');
for (var i = 0, l = list.length; i < l; i++) {
var bindData = list[i].getAttribute('ng-bind');
list[i].innerHTML = scope[bindData];
}
}
bind();
apply();
}
複製代碼
沒錯,我只是我偷懶實現的……其中還有不少bug,雖然實現了頁面效果,可是仍然有不少缺陷,好比方法我直接就定義在了scope
裏面,能夠說,這一套代碼是我爲了實現雙向綁定而實現的雙向綁定。this
回到主題,這段代碼中我有用到髒檢查嗎?spa
徹底沒有。
這段代碼的意思就是bind()
方法綁定click事件,apply()
方法顯示到了頁面上去而已。
OK,拋開這段代碼,先看2.0
版本的代碼
window.onload = function() {
function getNewValue(scope) {
return scope[this.name];
}
function $scope() {
// AngularJS裏,$$表示其爲內部私有成員
this.$$watchList = [];
}
// 髒檢查監測變化的一個方法
$scope.prototype.$watch = function(name, getNewValue, listener) {
var watch = {
// 標明watch對象
name: name,
// 獲取watch監測對象的值
getNewValue: getNewValue,
// 監聽器,值發生改變時的操做
listener: listener
};
this.$$watchList.push(watch);
}
$scope.prototype.$digest = function() {
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
list[i].listener();
}
}
// 下面是實例化內容
var scope = new $scope;
scope.$watch('first', function() {
console.log("I have got newValue");
}, function() {
console.log("I am the listener");
})
scope.$watch('second', function() {
console.log("I have got newValue =====2");
}, function() {
console.log("I am the listener =====2");
})
scope.$digest();
}
複製代碼
這個版本中,沒有數據雙向綁定的影子,這只是一個髒檢查的原理。
引入2.0版本,看看在控制檯發生了什麼。
控制檯打印出了 I am the listener
和 I am the listener =====2
這就說明,咱們的觀察成功了。
不過,僅此而已。
咱們光打印出來有用嗎?
明顯是沒有做用的。
接下來要來改寫這一段的方法。
首先,咱們要使 listener
起到觀察的做用。
先將 listener()
方法輸出內容改變,仿照 AngularJS
的 $watch
方法,只傳兩個參數:
scope.$watch('first', function(newValue, oldValue) {
console.log("new: " + newValue + "=========" + "old: " + oldValue);
})
scope.$watch('second', function(newValue, oldValue) {
console.log("new2: " + newValue + "=========" + "old2: " + oldValue);
})
複製代碼
再將 $digest
方法進行修改
$scope.prototype.$digest = function() {
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
// 獲取watch對應的對象
var watch = list[i];
// 獲取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last;
// 進行髒檢查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
}
// list[i].listener();
}
}
複製代碼
最後將 getNewValue
方法綁定到 $scope
的原型上,修改 watch
方法所傳的參數:
$scope.prototype.getNewValue = function(scope) {
return scope[this.name];
}
// 髒檢查監測變化的一個方法
$scope.prototype.$watch = function(name, listener) {
var watch = {
// 標明watch對象
name: name,
// 獲取watch監測對象的值
getNewValue: this.getNewValue,
// 監聽器,值發生改變時的操做
listener: listener
};
this.$$watchList.push(watch);
}
複製代碼
最後定義這兩個對象:
scope.first = 1;
scope.second = 2;
複製代碼
這個時候再運行一遍代碼,會發現控制檯輸出了 new: 1=========old: undefined
和 new2: 2=========old2: undefined
OK,代碼到這一步,咱們實現了watch觀察到了新值和老值。
這段代碼的 watch
我是手動觸發的,那個該如何進行自動觸發呢?
$scope.prototype.$digest = function() {
var list = this.$$watchList;
// 判斷是否髒了
var dirty = true;
while (dirty) {
dirty = false;
for (var i = 0; i < list.length; i++) {
// 獲取watch對應的對象
var watch = list[i];
// 獲取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last;
// 關鍵來了,進行髒檢查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
dirty = true;
}
// list[i].listener();
}
}
}
複製代碼
那我問一個問題,爲何我要寫兩個 watch
對象?
很簡單,若是我在 first
中改變了 second
的值,在 second
中改變了 first
的值,這個時候,會出現無限循環調用。
那麼,AngularJS
是如何避免的呢?
$scope.prototype.$digest = function() {
var list = this.$$watchList;
// 判斷是否髒了
var dirty = true;
// 執行次數限制
var checkTime = 0;
while (dirty) {
dirty = false;
for (var i = 0; i < list.length; i++) {
// 獲取watch對應的對象
var watch = list[i];
// 獲取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last;
// 關鍵來了,進行髒檢查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
dirty = true;
}
// list[i].listener();
}
checkTime++;
if (checkTime > 10 && checkTime) {
throw new Error("次數過多!")
}
}
}
複製代碼
scope.$watch('first', function(newValue, oldValue) {
scope.second++;
console.log("new: " + newValue + "=========" + "old: " + oldValue);
})
scope.$watch('second', function(newValue, oldValue) {
scope.first++;
console.log("new2: " + newValue + "=========" + "old2: " + oldValue);
})
複製代碼
這個時候咱們查看控制檯,發現循環了10次以後,拋出了異常。
這個時候,髒檢查機制已經實現,是時候將這個與第一段代碼進行合併了,3.0
代碼橫空出世。
window.onload = function() {
'use strict';
function Scope() {
this.$$watchList = [];
}
Scope.prototype.getNewValue = function() {
return $scope[this.name];
}
Scope.prototype.$watch = function(name, listener) {
var watch = {
name: name,
getNewValue: this.getNewValue,
listener: listener || function() {}
};
this.$$watchList.push(watch);
}
Scope.prototype.$digest = function() {
var dirty = true;
var checkTimes = 0;
while (dirty) {
dirty = this.$$digestOnce();
checkTimes++;
if (checkTimes > 10 && dirty) {
throw new Error("循環過多");
}
}
}
Scope.prototype.$$digestOnce = function() {
var dirty;
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
var watch = list[i];
var newValue = watch.getNewValue();
var oldValue = watch.last;
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
dirty = true;
} else {
dirty = false;
}
watch.last = newValue;
}
return dirty;
}
var $scope = new Scope();
$scope.sum = 0;
$scope.data = 0;
$scope.increase = function() {
this.data++;
};
$scope.decrease = function() {
this.data--;
};
$scope.equal = function() {
};
$scope.faciend = 3
$scope.$watch('data', function(newValue, oldValue) {
$scope.sum = newValue * $scope.faciend;
console.log("new: " + newValue + "=========" + "old: " + oldValue);
});
function bind() {
var list = document.querySelectorAll('[ng-click]');
for (var i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function(index) {
return function() {
var func = this.getAttribute('ng-click');
$scope[func]($scope);
$scope.$digest();
apply();
}
})(i)
}
}
function apply() {
var list = document.querySelectorAll('[ng-bind]');
for (var i = 0, l = list.length; i < l; i++) {
var bindData = list[i].getAttribute('ng-bind');
list[i].innerHTML = $scope[bindData];
}
}
bind();
$scope.$digest();
apply();
}
複製代碼
頁面上將 合計 放開,看看會有什麼變化。
這就是 AngularJS
髒檢查機制的實現,固然,Angular
裏面確定比我要複雜的多,可是確定是基於這個進行功能的增長,好比 $watch
傳的第三個參數。
如今 Angular
已經發展到了 Angular6
,可是谷歌仍然在維護 AngularJS
,並且,並不必定框架越新技術就必定越先進,要看具體的項目是否適合。
好比說目前最火的 React
,它採用的是虛擬DOM
,簡單來講就是將頁面上的DOM
和JS
裏面的虛擬DOM
進行對比,而後將不同的地方渲染到頁面上去,這個思想就是AngularJS
的髒檢查機制,只不過AngularJS
是檢查的數據,React
是檢查的DOM
而已。