天天看點

說說AngularJs——自定義指令(二)

上一篇簡單介紹了自定義一個指令的幾個簡單參數,restrict、template、templateUrl、replace、transclude,這幾個了解起來相對容易很多,因為它們隻涉及到了表現,而沒有涉及行為。這一篇将繼續學習ng自定義指令的幾個重量級參數,了解了它們之後我們的custom directive将不光能“看”,還要能“動”。開始~

了解compile和link

  不知大家有沒有這樣的感覺,自己定義指令的時候跟寫jQuery插件有幾分相似之處,都是先預先定義好頁面結構及監聽函數,然後在某個元素上調用一下,該元素便擁有了特殊的功能。差別在于,jQuery的側重點是DOM操作,而ng的指令中除了可以進行DOM操作外,更注重的是資料和模闆的綁定。jQuery插件在調用的時候才開始初始化,而ng指令在頁面加載進來的時候就被編譯服務($compile)初始化好了。

  在指令定義對象中,有compile和link兩個參數,它們是做什麼的呢?從字面意義上看,編譯、連結,貌似太抽象了點。其實可大有内涵,為了在自定義指令的時候能正确使用它們,現在有必要了解一下ng是如何編譯指令的。上一篇我有列了一下指令的執行流程,但僅僅列1234有點太對不起觀衆了,故在此詳細分析一下。此知識點相當重要。

指令的解析流程詳解

  我們知道ng架構會在頁面載入完畢的時候,根據ng-app劃定的作用域來調用$compile服務進行編譯,這個$compile就像一個大總管一樣,清點作用域内的DOM元素,看看哪些元素上使用了指令(如<div ng-modle=”m”></div>),或者哪些元素本身就是個指令(如<mydierc></mydirec>),或者使用了插值指令( {{}}也是一種指令,叫interpolation directive),$compile大總管會把清點好的财産做一個清單,然後根據這些指令的優先級(priority)排列一下,真是個細心的大總管哈~大總管還會根據指令中的配置參數(template,place,transclude等)轉換DOM,讓指令“初具人形”。

  然後就開始按順序執行各指令的compile函數,注意此處的compile可不是大總管$compile,人家帶着$是土豪,此處執行的compile函數是我們指令中配置的,compile函數中可以通路到DOM節點并進行操作,其主要職責就是進行DOM轉換,每個compile函數執行完後都會傳回一個link函數,這些link函數會被大總管彙合一下組合成一個合體後的link函數,為了好了解,我們可以把它想象成葫蘆小金剛,就像是進行了這樣的處理

//合體後的link函數
function AB(){
  A(); //子link函數
  B(); //子link函數
}
           

接下來進入link階段,合體後的link函數被執行。所謂的連結,就是把view和scope連結起來。連結成啥樣呢?就是我們熟悉的資料綁定,通過在DOM上注冊監聽器來動态修改scope中的資料,或者是使用$watchs監聽 scope中的變量來修改DOM,進而建立雙向綁定。由此也可以斷定,葫蘆小金剛可以通路到scope和DOM節點。

  不要忘了我們在定義指令中還配置着一個link參數呢,這麼多link千萬别搞混了。那這個link函數是幹嘛的呢,我們不是有葫蘆小金剛了嘛?那我告訴你,其實它是一個小三。此話怎講?compile函數執行後傳回link函數,但若沒有配置compile函數呢?葫蘆小金剛自然就不存在了。正房不在了,當然就輪到小三出馬了,大總管$compile就把這裡的link函數拿來執行。這就意味着,配置的link函數也可以通路到scope以及DOM節點。值得注意的是,compile函數通常是不會被配置的,因為我們定義一個指令的時候,大部分情況不會通過程式設計的方式進行DOM操作,而更多的是進行監聽器的注冊、資料的綁定。是以,小三名正言順的被大總管寵愛~

  聽完了大總管、葫蘆小金剛和小三的故事,你是不是對指令的解析過程比較清晰了呢?不過細細推敲,你可能還是會覺得情節生硬,有些細節似乎還是沒有透徹的明白,是以還需要再了解下面的知識點:

compile和link的差別

  其實在我看完官方文檔後就一直有疑問,為什麼監聽器、資料綁定不能放在compile函數中,而偏偏要放在link函數中?為什麼有了compile還需要link?就跟你質疑我編的故事一樣,為什麼最後小三被寵愛了?是以我們有必要探究一下,compile和link之間到底有什麼差別。好,正房與小三的PK現在開始。

         首先是性能。舉個例子:

<ul>
  <li ng-repeat="a in array">
    <input ng-modle=”a.m” />
  </li>
</ul>
           

我們的觀察目标是ng-repeat指令。假設一個前提是不存在link。大總管$compile在編譯這段代碼時,會查找到ng-repeat,然後執行它的compile函數,compile函數根據array的長度複制出n個<li>标簽。而複制出的<li>節點中還有<input>節點并且使用了ng-modle指令,是以compile還要掃描它并比對指令,然後綁定監聽器。每次循環都做如此多的工作。而更加糟糕的一點是,我們會在程式中向array中添加元素,此時頁面上會實時更新DOM,每次有新元素進來,compile函數都把上面的步驟再走一遍,豈不是要累死了,這樣性能必然不行。

         現在扔掉那個假設,在編譯的時候compile就隻管生成DOM的事,碰到需要綁定監聽器的地方先存着,有幾個存幾個,最後把它們彙總成一個link函數,然後一并執行。這樣就輕松多了,compile隻需要執行一次,性能自然提升。

         另外一個差別是能力。盡管compile和link所做的事情差不多,但它們的能力範圍還是不一樣的。比如正房能管你的存款,小三就不能。小三能給你初戀的感覺,正房卻不能。

         我們需要看一下compile函數和link函數的定義:

function compile(tElement, tAttrs, transclude) { ... }

function link(scope, iElement, iAttrs, controller) { ... } 
           

  這些參數都是通過依賴注入而得到的,可以按需聲明使用。從名字也容易看出,兩個函數各自的職責是什麼,compile可以拿到transclude,允許你自己程式設計管理乾坤大挪移的行為。而link中可以拿到scope和controller,可以與scope進行資料綁定,與其他指令進行通信。兩者雖然都可以拿到element,但是還是有差別的,看到各自的字首了吧?compile拿到的是編譯前的,是從template裡拿過來的,而link拿到的是編譯後的,已經與作用域建立了關聯,這也正是link中可以進行資料綁定的原因。

  正房與小三的差別就是性能和能力兩個關鍵字,簡記為性能力,我想你永遠都不會忘記了吧,真相就是如此的赤裸裸啊~哈哈

  我暫時隻能了解到這個程度了。實在不想了解這些知識的話,隻要簡單記住一個原則就行了:如果指令隻進行DOM的修改,不進行資料綁定,那麼配置在compile函數中,如果指令要進行資料綁定,那麼配置在link函數中。

繼續閱讀