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