javascript
手写AngularJS脏检查机制
什么是臟檢查
View -> Model
瀏覽器提供有User Event觸發事件的API,例如,click,change等
Model -> View
瀏覽器沒有數據監測API。
AngularJS 提供了 $apply(),$digest(),$watch()。
其他數據雙向綁定介紹
VUE
{{}} Object.defineProperty() 中使用 setter / getter 鉤子實現。
Angular
[()] 事件綁定加上屬性綁定構成雙向綁定。
怎么手寫
大家先看運行效果,運行后,點增加,數字會+1,點減少,數字會-1,就是這么一個簡單的頁面,視圖到底為何會自動更新數據呢?
我先把最粗糙的源碼放出來,大家先看看,有看不懂得地方再議。
老規矩,初始化頁面
<!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版本
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里面,可以說,這一套代碼是我為了實現雙向綁定而實現的雙向綁定。
回到主題,這段代碼中我有用到臟檢查嗎?
完全沒有。
這段代碼的意思就是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 已經發展到了 Angular5,但是谷歌仍然在維護 AngularJS,而且,并不一定框架越新技術就一定越先進,要看具體的項目是否適合。
比如說目前最火的 React ,它采用的是虛擬DOM,簡單來說就是將頁面上的DOM和JS里面的虛擬DOM進行對比,然后將不一樣的地方渲染到頁面上去,這個思想就是AngularJS的臟檢查機制,只不過AngularJS是檢查的數據,React是檢查的DOM而已。
總結
以上是生活随笔為你收集整理的手写AngularJS脏检查机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: node封装mysql模块
- 下一篇: 解决“安装VMM过程中无法注册SPN以及