天天看點

Angularjs 源碼分析3

上次說到了<code>rootscope</code>裡的<code>$watch</code>方法中的解析監控表達式,即而引出了對<code>parse</code>的分析,今天我們接着這裡繼續挖代碼.

先上一塊<code>$watch</code>代碼

這裡的<code>get = compiletofn(watchexp, 'watch')</code>,上篇已經分析完了,這裡傳回的是一個執行表達式的函數,接着往下看,這裡初始化了一個<code>watcher</code>對象,用來儲存一些監聽相關的資訊,簡單的說明一下

fn, 代表監聽函數,當監控表達式新舊不相等時會執行此函數

last, 儲存最後一次發生變化的監控表達式的值

get, 儲存一個監控表達式對應的函數,目的是用來擷取表達式的值然後用來進行新舊對比的

exp, 儲存一個原始的監控表達式

eq, 儲存<code>$watch</code>函數的第三個參數,表示是否進行深度比較

然後會檢查傳遞進來的監聽參數是否為函數,如果是一個有效的字元串,則通過<code>parse</code>來解析生成一個函數,否則指派為一個<code>noop</code>占位函數,最後生成一個包裝函數,函數體的内容就是執行剛才生成的監聽函數,預設傳遞目前作用域.

接着會檢查監控表達式是否為字元串并且執行表達式的<code>constant</code>為true,代表這個字元串是一個常量,那麼,系統在處理這種監聽的時候,執行完一次監聽函數之後就會删除這個<code>$watch</code>.最後往目前作用域裡的<code>$$watchers</code>數組頭中添加<code>$watch</code>資訊,注意這裡的傳回值,利用js的閉包保留了目前的<code>watcher</code>,然後傳回一個函數,這個就是用來删除監聽用的.

這個<code>$eval</code>也是挺友善的函數,假如你想直接在程式裡執行一個字元串的話,那麼可以這麼用

大家來看看它的函數體

其實就是通過<code>parse</code>來解析成一個執行表達式函數,然後傳遞目前作用域以及額外的參數,傳回這個執行表達式函數的值

<code>evalasync</code>函數的作用就是延遲執行表達式,并且執行完不管是否異常,觸發<code>dirty check</code>.

可以看到目前作用域内部有一個<code>$$asyncqueue</code>異步隊列,儲存着所有需要延遲執行的表達式,此處的表達式可以是字元串或者函數,因為這個表達式最終會調用<code>$eval</code>方法,注意這裡調用了<code>$browser</code>服務的<code>defer</code>方法,從<code>ng-&gt;browser.js</code>源碼裡可以看到,其實這裡就是調用<code>settimeout</code>來實作的.

上面的代碼主要是延遲執行函數,另外<code>pendingdeferids</code>對象儲存所有<code>settimeout</code>傳回的id,這個會在<code>self.defer.cancel</code>這裡可以取消執行延遲執行.

說<code>digest</code>方法之前,還有一個方法要說說

這個方法跟<code>evalasync</code>不同的時,它不會主動觸發<code>digest</code>方法,隻是往<code>postdigestqueue</code>隊列中增加執行表達式,它會在<code>digest</code>體内最後執行,相當于在觸發<code>dirty check</code>之後,可以執行别的一些邏輯.

下面我們來重點說說<code>digest</code>方法

<code>digest</code>方法是<code>dirty check</code>的核心,主要思路是先執行<code>$$asyncqueue</code>隊列中的表達式,然後開啟一個<code>loop</code>來的執行所有的<code>watch</code>裡的監聽函數,前提是前後兩次的值是否不相等,假如<code>ttl</code>超過系統預設值,則<code>dirth check</code>結束,最後執行<code>$$postdigestqueue</code>隊列裡的表達式.

通過上面的代碼,可以看出,核心就是兩個loop,外loop保證所有的model都能檢測到,内loop則是真實的檢測每個<code>watch</code>,<code>watch.get</code>就是計算監控表達式的值,這個用來跟舊值進行對比,假如不相等,則執行監聽函數

注意這裡的<code>watch.eq</code>這是是否深度檢查的辨別,<code>equals</code>方法是<code>angular.js</code>裡的公共方法,用來深度對比兩個對象,這裡的不相等有一個例外,那就是<code>nan ===nan</code>,因為這個永遠都是<code>false</code>,是以這裡加了檢查

比較完之後,把新值傳給<code>watch.last</code>,然後執行<code>watch.fn</code>也就是監聽函數,傳遞三個參數,分别是:最新計算的值,上次計算的值(假如是第一次的話,則傳遞新值),最後一個參數是目前作用域執行個體,這裡有一個設定外<code>loop</code>的條件值,那就是<code>dirty = true</code>,也就是說隻要内loop執行了一次<code>watch</code>,則外loop還要接着執行,這是為了保證所有的model都能監測一次,雖然這個有點浪費性能,不過超過<code>ttl</code>設定的值後,<code>dirty check</code>會強制關閉,并抛出異常

這裡的<code>watchlog</code>日志對象是在内loop裡,當<code>ttl</code>低于5的時候開始記錄的

當檢查完一個作用域内的所有<code>watch</code>之後,則開始深度周遊目前作用域的子級或者父級,雖然這有些影響性能,就像這裡的注釋寫的那樣<code>yes, this code is a bit crazy</code>

上面的代碼其實就是不斷的查找目前作用域的子級,沒有子級,則開始查找兄弟節點,最後查找它的父級節點,是一個深度周遊查找.隻要<code>next</code>有值,則内loop則一直執行

不過内loop也有跳出的情況,那就是目前<code>watch</code>跟最後一次檢查的<code>watch</code>相等時就退出内loop.

注意這個内loop同時也是一個label(标簽)語句,這個可以在loop中執行跳出操作就像上面的<code>break</code>

正常執行完兩個loop之後,清除目前的階段辨別<code>clearphase();</code>,然後開始執行<code>postdigestqueue</code>隊列裡的表達式.

接下來說說,用的也比較多的<code>$apply</code>方法

這個方法一般用在,不在ng的上下文中執行js代碼的情況,比如原生的dom事件中執行想改變ng中某些model的值,這個時候就要使用<code>$apply</code>方法了

代碼中,首先讓目前階段辨別為<code>$apply</code>,這個可以防止使用<code>$apply</code>方法時檢查是否已經在這個階段了,然後就是執行<code>$eval</code>方法, 這個方法上面有講到,最後執行<code>$digest</code>方法,來使ng中的m或者vm改變.

接下來說說<code>scope</code>中<code>event</code>子產品,它的api跟一般的<code>event</code>事件子產品比較像,提供有<code>$on</code>,<code>$emit</code>,<code>$broadcast</code>,這三個很實用的方法

這個方法是用來定義事件的,這裡用到了兩個執行個體變量<code>$$listeners</code>, <code>$$listenercount</code>,分别用來儲存事件,以及事件數量計數

分析上面的代碼,可以看出每當定義一個事件的時候,都會向<code>$$listeners</code>對象中添加以<code>name</code>為key的屬性,值就是事件執行函數,注意這裡有個事件計數,隻要有父級,則也給父級的<code>$$listenercount</code>添加以<code>name</code>為key的屬性,并且值<code>+1</code>,這個<code>$$listenercount</code> 會在廣播事件的時候用到,最後這個方法傳回一個取消事件的函數,先設定<code>$$listeners</code>中以<code>name</code>為key的值為<code>null</code>,然後調用<code>decrementlistenercount</code>來使該事件計數<code>-1</code>.

這個方法是用來觸發<code>$on</code>定義的事件,原理就是loop<code>$$listeners</code>屬性,檢查是否有值,有的話,則執行,然後依次往上檢查父級,這個方法有點類似冒泡執行事件.

上面的代碼比較簡單,首先定義一個事件參數,然後開啟一個loop,隻要<code>scope</code>有值,則一直執行,這個方法的事件鍊是一直向上傳遞的,不過當在事件函數執行<code>stoppropagation</code>方法,就會停止向上傳遞事件.

這個是<code>$emit</code>的更新版,廣播事件,即能向上傳遞,也能向下傳遞,還能平級傳遞,核心原理就是利用深度周遊目前作用域

代碼跟<code>$emit</code>差不多,隻是跟它不同的時,這個是不斷的取<code>next</code>值,而<code>next</code>的值則是通過深度周遊它的子級節點,兄弟節點,父級節點,依次查找可用的以<code>name</code>為key的事件.注意這裡的注釋,跟<code>$digest</code>裡的差不多,都是通過深度周遊查找,是以<code>$broadcast</code>方法也不能常用,性能不是很理想

這個方法是用來銷毀目前作用域,代碼主要是清空目前作用域内的一些執行個體屬性,以免執行<code>digest</code>,<code>$emit</code>,<code>$broadcast</code>時會關聯到

代碼比較簡單,先是通過<code>foreach</code>來清空<code>$$listenercount</code>執行個體屬性,然後再設定<code>$parent</code>,<code>$$nextsibling</code>,<code>$$prevsibling</code>,<code>$$childhead</code>,<code>$$childtail</code>,<code>$root</code>為<code>null</code>,清空<code>$$listeners</code>,<code>$$watchers</code>,<code>$$asyncqueue</code>,<code>$$postdigestqueue</code>,最後就是重罷方法為<code>noop</code>占位函數

<code>rootscope</code>說完了,這是個使用比例非常高的核心<code>provider</code>,分析的比較簡單,有啥錯誤的地方,希望大家能夠指出來,大家一起學習學習,下次有空接着分析别的.

繼續閱讀