摘要:換言之,的對應(yīng)的,此外它還有。它們共同構(gòu)成的監(jiān)控系統(tǒng)。和是相輔相成的。兩者一起,構(gòu)成了作用域的核心功能數(shù)據(jù)變化的響應(yīng)。迭代的最大值稱為。框架設(shè)計第三版,敬請期待
angular的ViewModel有一個專門的官方術(shù)語叫$scope, 它只是一個普通構(gòu)造器(Scope)的實例。換言之,它是一個普通的JS對象。為了實現(xiàn)MVVM框架通常宣傳的那種“改變數(shù)據(jù)即改變視圖”的魔幻效果,它得裝備上更多更強大的外掛。
名:
姓:
姓名: {{firstName + " " + lastName}}
app.controller會產(chǎn)生一個$scope對象, 這個$scope是傳進去的。相當(dāng)于:
var $scope = new Scope(); $scope.firstName = "Jane"; $scope.lastName = "Smith";
相對于avalon將所有vm扁平化地放到avalon.vmodels中,angular則傾向?qū)?b>$scope對象以樹的形式組織起來。
function Scope() { this.$id = nextUid(); this.$$phase = this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = false; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; this.$$isolateBindings = null; }
其中$parent、$$nextSibling, $$prevSibling, $$childHead, $$childTail,$root是指向其他$scope對象。 $$watchers是綁定對象的訂閱數(shù)組,$$watchersCount是其長度, $$listeners 是放手動觸發(fā)的函數(shù),$$listenerCount是其長度。
由于angular是一個普通的JS對象,當(dāng)屬性發(fā)生變化時,它本身不可能像avalon那么靈敏地跑去$fire。 于是它實現(xiàn)了一套復(fù)雜的$fire方法,但它不叫$fire, 叫做$digest。
換言之,avalon的$watch對應(yīng)angular的$watch,此外它還有$watchGroup, $watchCollection。avalon的$fire方法對應(yīng)angular的$digest, 為了安全,它外面還有$applyAsync, $apply, $evalAsync等幾個殼函數(shù)。它們共同構(gòu)成angular的監(jiān)控系統(tǒng)。$watch和$digest是相輔相成的。兩者一起,構(gòu)成了angular作用域的核心功能:數(shù)據(jù)變化的響應(yīng)。
先看$watch方法, 傳參比avalon復(fù)雜多,但結(jié)果都是返回一個移除監(jiān)聽的函數(shù):
Scope.prototype.$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { //將表達(dá)式轉(zhuǎn)換為求值函數(shù) var get = $parse(watchExp); if (get.$$watchDelegate) { return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); } var scope = this, //所有綁定對象都放在一個數(shù)組中,因此存在性能問題 array = scope.$$watchers, //構(gòu)建綁定對象 watcher = { fn: listener,//刷新函數(shù) last: initWatchVal,//舊值 get: get,//求值函數(shù) exp: prettyPrintExpression || watchExp,//表達(dá)式 eq: !!objectEquality// 比較方法 }; lastDirtyWatch = null; if (!isFunction(listener)) { watcher.fn = noop; } if (!array) { array = scope.$$watchers = []; } array.unshift(watcher); incrementWatchersCount(this, 1); return function deregisterWatch() {//移除綁定對象 if (arrayRemove(array, watcher) >= 0) { incrementWatchersCount(scope, -1); } lastDirtyWatch = null; }; },
而$digest則復(fù)雜多了,我們先實現(xiàn)它的一個簡化版,遍歷其所有綁定對象,執(zhí)行其刷新函數(shù)。
Scope.prototype.$digest = function() { var list = this.$$watchers || [] list.forEach(function(watch) { var newValue = watch.get() var oldValue = watch.last; if (newValue !== oldValue) { watch.fn(newValue, oldValue, self); } watch.last = newValue; }) }
到目前為止,它的邏輯與 avalon的一樣,但要明白一點,avalon的監(jiān)控是智能的,如果更新A屬性,導(dǎo)致了B屬性也發(fā)生變化,那么avalon也連忙更新B涉及的視圖。而angular的$$watcher 里面都是一個個普通對象,假如里面有A,B兩個對象。先執(zhí)行A,A值沒有變化,再執(zhí)行B,B變化了,但B在變化時的同時,也修改了A值。但這時,循環(huán)已經(jīng)完畢。B涉及的視圖變動 ,A沒有變動,這就不合理了。因此,我們需要在某個綁定對象發(fā)生了一次改動后,再重新檢測這個數(shù)組。
我們把現(xiàn)在的$digest函數(shù)改名為$$digestOnce,它把所有的監(jiān)聽器運行一次,返回一個布爾值,表示是否還有變更了:
Scope.prototype.$$digestOnce = function() { var self = this; var dirty; _.forEach(this.$$watchers, function(watch) { var newValue = watch.get(); var oldValue = watch.last; if (newValue !== oldValue) { watch.fn(newValue, oldValue, self); dirty = true; } watch.last = newValue; }); return dirty; };
然后,我們重新定義$digest,它作為一個“外層循環(huán)”來運行,當(dāng)有變更發(fā)生的時候,調(diào)用$$digestOnce:
Scope.prototype.$digest = function() { var dirty; do { dirty = this.$$digestOnce(); } while (dirty); };
$digest現(xiàn)在至少運行每個監(jiān)聽器一次了。如果第一次運行完,有監(jiān)控值發(fā)生變更了,標(biāo)記為dirty,所有監(jiān)聽器再運行第二次。這會一直運行,直到所有監(jiān)控的值都不再變化,整個局面穩(wěn)定下來了。
但這里面有一個風(fēng)險,比如A的求值函數(shù)里會修改B, B的求值函數(shù)又修改A,那么大家都無法穩(wěn)定下來,不斷死循環(huán)。因此我們得把digest的運行控制在一個可接受的迭代數(shù)量內(nèi)。如果這么多次之后,作用域還在變更,就勇敢放手,宣布它永遠(yuǎn)不會穩(wěn)定。在這個點上,我們會拋出一個異常,因為不管作用域的狀態(tài)變成怎樣,它都不太可能是用戶想要的結(jié)果。
迭代的最大值稱為TTL(short for Time To Live)。這個值默認(rèn)是10,可能有點小(我們剛運行了這個digest 成千上萬次),但是記住這是一個性能敏感的地方,因為digest經(jīng)常被執(zhí)行,而且每個digest運行了所有的監(jiān)聽器。
Scope.prototype.$digest = function() { var ttl = 10; var dirty; do { dirty = this.$$digestOnce(); if (dirty && !(ttl--)) { throw "10 digest iterations reached"; } } while (dirty); };
但這只是模擬了angular的$digest的冰山一角,可見沒有訪問器屬性這高階魔法,想實現(xiàn)MVVM是非常麻煩與復(fù)雜,并且用戶使用起來也別扭。
有關(guān)$digest的源碼與解決可見這里
https://github.com/angular/an...
http://www.cnblogs.com/xuezhi...
我們再看$digest 是怎么與angular的ng-model 關(guān)聯(lián)在一起。
ng-model指令有一個$post方法,它在里面進行綁定事件,如果用戶提供了updateOn這個選項,選項是一些事件名,那么它就為元素綁定對應(yīng)的事件,否則就綁定blur方法
post: function ngModelPostLink(scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; if (modelCtrl.$options.getOption("updateOn")) { element.on(modelCtrl.$options.getOption("updateOn"), function(ev) { modelCtrl.$$debounceViewValueCommit(ev && ev.type); }); } function setTouched() { modelCtrl.$setTouched(); } element.on("blur", function() { if (modelCtrl.$touched) return; if ($rootScope.$$phase) { scope.$evalAsync(setTouched); } else { scope.$apply(setTouched); } }); }
我們先看blur的回調(diào),里面$evalAsync與$apply方法,它們里面就會調(diào)用$digest,進行臟檢測。
$evalAsync: function(expr, locals) { if (!$rootScope.$$phase && !asyncQueue.length) { $browser.defer(function() { if (asyncQueue.length) { $rootScope.$digest(); } }); } //...略 }, $apply: function(expr) { try { beginPhase("$apply"); //...略 } finally { try { $rootScope.$digest(); } catch (e) { $exceptionHandler(e); throw e; } } },
再看$$debounceViewValueCommit方法,里面也有一個$apply方法。換言之,殊途同歸,全部匯在$digest里面處理。
但如果許多地方同時發(fā)生改變,會不會將它搞死呢?不會,我們留意一下$digest的源碼最上方有一句 beginPhase("$digest"),臨結(jié)束時也有一句clearPhase()。$apply 里面也是 beginPhase("$apply")與clearPhase(),它們標(biāo)識這個$scope對象進行臟檢測,直接拋錯。
function beginPhase(phase) { if ($rootScope.$$phase) { throw $rootScopeMinErr("inprog", "{0} already in progress", $rootScope.$$phase); } $rootScope.$$phase = phase; } function clearPhase() { $rootScope.$$phase = null; }
但$apply會將錯誤catch住,不讓它影響程序繼續(xù)運行。這就是官方推我們使用$apply驅(qū)動程序運行,而不直接用$digest的緣故。
通過上面的分析,avalon與angular的設(shè)計重點是不同的,avalon是忙于發(fā)掘語言特征,通過訪問器中的setter與getter將其那個簡單的觀察者模式放進去。angular則忙于構(gòu)建其復(fù)雜無比的觀察者模式(本節(jié)沒有展現(xiàn)其全貌,它除了$$watchers隊列,還有asyncQueue隊列,postDigestQueue隊列,applyAsyncQueue隊列), 并且為了diff新舊值的不同,發(fā)展出一套名叫臟檢測的機制。
from 《javascript框架設(shè)計》第三版,敬請期待
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/87933.html
摘要:也放出地址,上面有完整工程以及在線演示地址相關(guān)閱讀教學(xué)向行代碼教你實現(xiàn)一個低配版的庫原理篇教學(xué)向行代碼教你實現(xiàn)一個低配版的庫代碼篇教學(xué)向再加行代碼教你實現(xiàn)一個低配版的庫設(shè)計篇教學(xué)向再加行代碼教你實現(xiàn)一個低配版的庫原理篇 書接上一篇: 150行代碼教你實現(xiàn)一個低配版的MVVM庫(1)- 原理篇 寫在前面 為了便于分模塊,和閱讀,我使用了Typescript來進行coding,總行數(shù)是正好...
摘要:,的事件回調(diào)函數(shù)中調(diào)用的操作方法。以為例調(diào)用關(guān)系模式實際就是將中的改名為,調(diào)用過程基本一致,最大的改良是間的雙向綁定。和間,有一個對象,可以操作修改,使用。 參考:MVC,MVP 和 MVVM 的圖示 - 阮一峰http://www.ruanyifeng.com/blo...Web開發(fā)的MVVM模式http://www.cnblogs.com/dxy198...界面之下:還原真實的MV...
摘要:,的事件回調(diào)函數(shù)中調(diào)用的操作方法。以為例調(diào)用關(guān)系模式實際就是將中的改名為,調(diào)用過程基本一致,最大的改良是間的雙向綁定。和間,有一個對象,可以操作修改,使用。 參考:MVC,MVP 和 MVVM 的圖示 - 阮一峰http://www.ruanyifeng.com/blo...Web開發(fā)的MVVM模式http://www.cnblogs.com/dxy198...界面之下:還原真實的MV...
摘要:是什么為什么我們要使用說到了,我們就不得不先聊一下是什么以及為什么我們要使用,他能給我們的開發(fā)帶來什么樣的便利呢首先,我們來看一下的自我介紹讀音,類似于是一套用于構(gòu)建用戶界面的漸進式框架。 作為一個剛?cè)胄胁痪玫牟锁B不知從什么時候開始就有了寫一個自己的專欄的想法,剛好今天沒事就給自己挖一個坑,分享一下我對vue的見解和一些領(lǐng)悟,整個專欄應(yīng)該會包括vue,vue-cli,vue-route...
閱讀 2760·2021-11-22 14:45
閱讀 906·2021-10-15 09:41
閱讀 1068·2021-09-27 13:35
閱讀 3690·2021-09-09 11:56
閱讀 2634·2019-08-30 13:03
閱讀 3199·2019-08-29 16:32
閱讀 3307·2019-08-26 13:49
閱讀 773·2019-08-26 10:35