這篇文章是我兩年前在博客園寫的,如今移植過來,不過Angular 1.x 在國內用的人已經很少了,但願能幫助到有須要的人
在 angular 的服務中,有一些服務你不得不去了解,由於他能夠說是 ng 的核心,而今天,我要介紹的就是 ng 的兩個核心服務,$parse
和 $compile
。其實這兩個服務講的人已經不少了,可是 100 個讀者就有 100 個哈姆雷特,我在這裏講講本身對於他們兩個服務的理解。html
你們可能會疑問,$eval
呢,其實他並非一個服務,他是 scope 裏面的一個方法,並不能算服務,並且它也基於 parse 的,因此只能算是$parse 的另外一種寫法而已,咱們看一下 ng 源碼中 $eval
的定義是怎樣的就知道了編程
$eval: function(expr, locals) { return $parse(expr)(this, locals); }
相信看完源碼你們就明白了吧,好了,如今就開始兩種核心服務的講解了,若是感受我說的不對的話,歡迎在評論區或者私聊指出,省得禍害其餘讀者。json
再講這兩個服務的時候,我要先講一個在本貼的概念:上下文app
我相信,不少人都聽過這個「上下文」,可是可能有點模糊,在我這裏給你們解釋解釋看看你們接不接受這個說法。dom
還記得 angular 的數據綁定嗎?好比:我如今有個有個叫 TestCtrl 的控制器,他的內容以下:函數
.controller('TestCtrl', function($scope) { $scope.test = "Boo!!!" })
而在 html 中咱們的代碼是這樣的post
<body ng-controller="TestCtrl"> {{test}} </body>
那麼,你們不用想都知道結果了,頁面上確定會顯示 Boo!!!
的字樣。測試
可是若是我刪掉 ng-controller 的指令呢?也就是我沒有在 html 申明控制器,你直接綁定{{test}}會如何呢?this
結果只有一個,那就是頁面啥都沒有(ps:由於你申明瞭 ng-app)。講到這裏你們明白了嗎?code
控制器就至關於一個上下文的容器,真正的上下文實際上是 $scope
,當頁面綁定 test,若是申明瞭控制器,當前上下文就是控制器裏面的$scope
,ng 會去找一下你這個控制器的上下文$scope 有沒有 test,若是有,他固然就顯示出來了,可是你不申明控制器的時候呢?他的上下文容器就是 ng-app 了,那麼他真正的上下文就是 $rootScope
,這個時候他就會尋找 $rootScope
有沒有 test。
好了,上下文的概念已經講完了,其實挺容易理解的,基本上和 this
很是類似
那麼言歸正傳,咱們開始講 $parse
,首先咱們要看的是 ng 的 API 文檔
var getter = $parse('user.name'); var setter = getter.assign; var context = {user:{name:'angular'}}; var locals = {user:{name:'local'}}; expect(getter(context)).toEqual('angular'); setter(context, 'newValue'); expect(context.user.name).toEqual('newValue'); expect(getter(context, locals)).toEqual('local');
你們看到的是 ng 文檔裏面對於$parse 服務性價比最高的幾行代碼,
getter
和 setter
就是你們所熟知的 get 方法和 set 方法了,context
和 locals
僅僅是 json 對象而已,目的就是模擬上下文關係
你們看到的下面四個語句最終都能經過測試,如今咱們一個個來分析,分析以前我要解釋一遍什麼叫 $parse
$parse
服務其實就是一種解析表達式的功能,就像 ng-model=「test」,你在 html 中寫這個東西誰知道你 ng-model=「test」中,其實你想綁定的是當前控制器(上下文容器)中 scope(上下文)中的 test 裏面的值,ng 就是經過$parse 服務去幫助你解析這個表達式的,因此在調用$parse 服務的時候你須要傳遞上下文對象,讓 ng 知道你是要去哪裏的 scope(上下文)去找你這個 test。
因此咱們看到第一行測試代碼是這樣的:
getter(context)).toEqual('angular') //實際上就是 $parse('user.name')(context)
在這個 context 就是上下文,他能返回「angular「這個字符串的原理就是通過這三步的:
因此這句測試代碼是成功的。
咱們看第二個方法 setter 方法
setter(context, 'newValue'); //實際上就是 $parse('user.name').assign(context, 'newValue') expect(context.user.name).toEqual('newValue'); //測試數據上下文的值是否被改變 這裏的 setter 方法實際上是改變值得方法
因而上下文對象發生了改變,從新用 getter 方法去獲取表達式的時候,上下文已經從 {user:{name:'angular'}} --> {user:{name:'newValue'}}
,最後獲取的表達式的值天然就是 newValue
了,因此測試代碼也是經過的。
expect(getter(context, locals)).toEqual('local');//實際上就是$parse('user.name')(context, locals)
這裏要表現的實際上是上下文的替換功能。
在 getter 的方法中咱們不只能夠選擇第一個上下文,可是若是咱們傳遞了第二個參數,那麼第一個上下文就會被第二個上下文覆蓋,注意是覆蓋.
從新回到 $eval
這個地方,咱們看待 $eval
源碼中能夠看出$eval 只有 get 功能,而沒有 set 功能,可是有些時候咱們能夠選擇傳遞第二個上下文,來達到修改值得效果。
在這裏 $parse
服務就已將說完了,接下來就是 $compile
若是你瞭解了 $parse
的概念以後,我想 $compile
也差很少理解了,其實和 $parse
很像。可是他是解析一段 html 代碼的,他的功能就是將死模板變成活模板,也是指令的核心服務。
好比你有一段 html 代碼 <h1>{{test}}</h1>
,若是你將這段代碼直接放在 html 代碼裏面,它所呈現的內容是怎樣的我不說你們也應該懂。這就是死模板了,而所謂的活模板,就是這裏面的數據所有通過了數據的綁定 {{test}}會自動找到當前的上下文,來綁定數據。最後顯示出來的 就是活模板,也就是通過數據綁定的模板。
$compile('死模板')(上下文對象)
,這樣就將死模板編程了活模板,你就能夠對這段活的 html 代碼作操做了,例如增長到當前節點,等等。
可是在指令中,她會返回兩個函數 pre-link
和 post-link
第一個執行的是 pre-link,它對於同一個指令的遍歷順序是從父節點到子節點的遍歷,在這個階段,dom 節點尚未穩定下來,沒法作一些綁定事件的操做,可是咱們能夠在這裏進行一些初始化數據的處理。
第二個執行的是 post-link,也就是咱們常說的 link 函數,他是從子節點到父節點遍歷的,在這個階段,DOM 節點已經穩定下來了,咱們通常會在這裏進行不少的操做。