javascript
手写AngularJS脏检查机制
什么是臟檢查
View -> Model
瀏覽器提供有User Event觸發(fā)事件的API,例如,click,change等
Model -> View
瀏覽器沒(méi)有數(shù)據(jù)監(jiān)測(cè)API。
AngularJS 提供了 $apply(),$digest(),$watch()。
其他數(shù)據(jù)雙向綁定介紹
VUE
{{}} Object.defineProperty() 中使用 setter / getter 鉤子實(shí)現(xiàn)。
Angular
[()] 事件綁定加上屬性綁定構(gòu)成雙向綁定。
怎么手寫(xiě)
大家先看運(yùn)行效果,運(yùn)行后,點(diǎn)增加,數(shù)字會(huì)+1,點(diǎn)減少,數(shù)字會(huì)-1,就是這么一個(gè)簡(jiǎn)單的頁(yè)面,視圖到底為何會(huì)自動(dòng)更新數(shù)據(jù)呢?
我先把最粗糙的源碼放出來(lái),大家先看看,有看不懂得地方再議。
老規(guī)矩,初始化頁(yè)面
<!DOCTYPE html> <html><head><meta charset="utf-8"><script src="./test_01.js" charset="utf-8"></script><title>手寫(xiě)臟檢查</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> 數(shù)量:<span ng-bind="data"></span></div><br><!-- 合計(jì) = <span ng-bind="sum"></span> --> </body></html>下面是JS源碼:1.0版本
window.onload = function() {'use strict';var scope = { // 相當(dāng)于$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(); }沒(méi)錯(cuò),我只是我偷懶實(shí)現(xiàn)的……其中還有很多bug,雖然實(shí)現(xiàn)了頁(yè)面效果,但是仍然有很多缺陷,比如方法我直接就定義在了scope里面,可以說(shuō),這一套代碼是我為了實(shí)現(xiàn)雙向綁定而實(shí)現(xiàn)的雙向綁定。
回到主題,這段代碼中我有用到臟檢查嗎?
完全沒(méi)有。
這段代碼的意思就是bind()方法綁定click事件,apply()方法顯示到了頁(yè)面上去而已。
OK,拋開(kāi)這段代碼,先看2.0版本的代碼
window.onload = function() {function getNewValue(scope) {return scope[this.name];}function $scope() {// AngularJS里,$$表示其為內(nèi)部私有成員this.$$watchList = [];}// 臟檢查監(jiān)測(cè)變化的一個(gè)方法$scope.prototype.$watch = function(name, getNewValue, listener) {var watch = {// 標(biāo)明watch對(duì)象name: name,// 獲取watch監(jiān)測(cè)對(duì)象的值getNewValue: getNewValue,// 監(jiān)聽(tīng)器,值發(fā)生改變時(shí)的操作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();}}// 下面是實(shí)例化內(nèi)容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(); }這個(gè)版本中,沒(méi)有數(shù)據(jù)雙向綁定的影子,這只是一個(gè)臟檢查的原理。
引入2.0版本,看看在控制臺(tái)發(fā)生了什么。
控制臺(tái)打印出了 I am the listener 和 I am the listener =====2 這就說(shuō)明,我們的觀察成功了。
不過(guò),僅此而已。
我們光打印出來(lái)有用嗎?
明顯是沒(méi)有作用的。
接下來(lái)要來(lái)改寫(xiě)這一段的方法。
首先,我們要使 listener 起到觀察的作用。
先將 listener() 方法輸出內(nèi)容改變,仿照 AngularJS 的 $watch 方法,只傳兩個(gè)參數(shù):
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 方法進(jìn)行修改
$scope.prototype.$digest = function() {var list = this.$$watchList;for (var i = 0; i < list.length; i++) {// 獲取watch對(duì)應(yīng)的對(duì)象var watch = list[i];// 獲取new和old的值var newValue = watch.getNewValue(this);var oldValue = watch.last;// 進(jìn)行臟檢查if (newValue !== oldValue) {watch.listener(newValue, oldValue);watch.last = newValue;}// list[i].listener();} }最后將 getNewValue 方法綁定到 $scope 的原型上,修改 watch 方法所傳的參數(shù):
$scope.prototype.getNewValue = function(scope) {return scope[this.name]; }// 臟檢查監(jiān)測(cè)變化的一個(gè)方法 $scope.prototype.$watch = function(name, listener) {var watch = {// 標(biāo)明watch對(duì)象name: name,// 獲取watch監(jiān)測(cè)對(duì)象的值getNewValue: this.getNewValue,// 監(jiān)聽(tīng)器,值發(fā)生改變時(shí)的操作listener: listener};this.$$watchList.push(watch); }最后定義這兩個(gè)對(duì)象:
scope.first = 1;scope.second = 2;這個(gè)時(shí)候再運(yùn)行一遍代碼,會(huì)發(fā)現(xiàn)控制臺(tái)輸出了 new: 1=========old: undefined 和 new2: 2=========old2: undefined
OK,代碼到這一步,我們實(shí)現(xiàn)了watch觀察到了新值和老值。
這段代碼的 watch 我是手動(dòng)觸發(fā)的,那個(gè)該如何進(jìn)行自動(dòng)觸發(fā)呢?
$scope.prototype.$digest = function() {var list = this.$$watchList;// 判斷是否臟了var dirty = true;while (dirty) {dirty = false;for (var i = 0; i < list.length; i++) {// 獲取watch對(duì)應(yīng)的對(duì)象var watch = list[i];// 獲取new和old的值var newValue = watch.getNewValue(this);var oldValue = watch.last;// 關(guān)鍵來(lái)了,進(jìn)行臟檢查if (newValue !== oldValue) {watch.listener(newValue, oldValue);watch.last = newValue;dirty = true;}// list[i].listener();}}}那我問(wèn)一個(gè)問(wèn)題,為什么我要寫(xiě)兩個(gè) watch 對(duì)象?
很簡(jiǎn)單,如果我在 first 中改變了 second 的值,在 second 中改變了 first 的值,這個(gè)時(shí)候,會(huì)出現(xiàn)無(wú)限循環(huán)調(diào)用。
那么,AngularJS 是如何避免的呢?
$scope.prototype.$digest = function() {var list = this.$$watchList;// 判斷是否臟了var dirty = true;// 執(zhí)行次數(shù)限制var checkTime = 0;while (dirty) {dirty = false;for (var i = 0; i < list.length; i++) {// 獲取watch對(duì)應(yīng)的對(duì)象var watch = list[i];// 獲取new和old的值var newValue = watch.getNewValue(this);var oldValue = watch.last;// 關(guān)鍵來(lái)了,進(jìn)行臟檢查if (newValue !== oldValue) {watch.listener(newValue, oldValue);watch.last = newValue;dirty = true;}// list[i].listener();}checkTime++;if (checkTime > 10 && checkTime) {throw new Error("次數(shù)過(guò)多!")}}} 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); })這個(gè)時(shí)候我們查看控制臺(tái),發(fā)現(xiàn)循環(huán)了10次之后,拋出了異常。
這個(gè)時(shí)候,臟檢查機(jī)制已經(jīng)實(shí)現(xiàn),是時(shí)候?qū)⑦@個(gè)與第一段代碼進(jìn)行合并了,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("循環(huán)過(guò)多");}}}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(); }頁(yè)面上將 合計(jì) 放開(kāi),看看會(huì)有什么變化。
這就是 AngularJS臟檢查機(jī)制的實(shí)現(xiàn),當(dāng)然,Angular 里面肯定比我要復(fù)雜的多,但是肯定是基于這個(gè)進(jìn)行功能的增加,比如 $watch 傳的第三個(gè)參數(shù)。
技術(shù)發(fā)展
現(xiàn)在 Angular 已經(jīng)發(fā)展到了 Angular5,但是谷歌仍然在維護(hù) AngularJS,而且,并不一定框架越新技術(shù)就一定越先進(jìn),要看具體的項(xiàng)目是否適合。
比如說(shuō)目前最火的 React ,它采用的是虛擬DOM,簡(jiǎn)單來(lái)說(shuō)就是將頁(yè)面上的DOM和JS里面的虛擬DOM進(jìn)行對(duì)比,然后將不一樣的地方渲染到頁(yè)面上去,這個(gè)思想就是AngularJS的臟檢查機(jī)制,只不過(guò)AngularJS是檢查的數(shù)據(jù),React是檢查的DOM而已。
總結(jié)
以上是生活随笔為你收集整理的手写AngularJS脏检查机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: node封装mysql模块
- 下一篇: 解决“安装VMM过程中无法注册SPN以及