上一篇簡單介紹了自定義一個指令的幾個簡單引數,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函式中。
無奈的結束
理解指令的處理流程以及compile和link的區別,花費了我大量的時間。資料太少了,官方文件翻來覆去看,最後理解到了這個程度,但總覺得還是差那麼一點,沒有100%理解到位。今天太困了,就記錄到此處吧,以後有了新的理解再做補充。
其實這篇的標題我想寫(下)的,直接把scope、require、controller一併介紹完畢,現在看來是不可能了,因為這幾個引數也是需要重點理解的,這樣下去篇幅又hold不住了。這裡就當是預報好了。。。囧。。。以前覺得沒有附圖和程式碼示例的部落格不是好部落格,現在反倒覺得要碼出這麼多字來,也是需要下功夫的。