angular1.x 中重要指令介紹($eval,$parse和$compile)

這篇文章是我兩年前在博客園寫的,如今移植過來,不過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 服務性價比最高的幾行代碼,

gettersetter 就是你們所熟知的 get 方法和 set 方法了,contextlocals 僅僅是 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「這個字符串的原理就是通過這三步的:

  1. 獲取當前的表達式 user.name
  2. 獲取當前的上下文對象{user:{name:'angular'}}
  3. 在上下問對象中尋找表達式,最終得到「angular「這個字符創

因此這句測試代碼是成功的。

咱們看第二個方法 setter 方法

setter(context, 'newValue');  //實際上就是 $parse('user.name').assign(context, 'newValue')
expect(context.user.name).toEqual('newValue');  //測試數據上下文的值是否被改變  這裏的 setter 方法實際上是改變值得方法
  1. 獲取當前的表達式 user.name
  2. 獲取當前的上下文對象{user:{name:'angular'}}
  3. 改變表達式中的值,將上下文對象編程{user:{name:'newValue'}}

因而上下文對象發生了改變,從新用 getter 方法去獲取表達式的時候,上下文已經從 {user:{name:'angular'}} --> {user:{name:'newValue'}},最後獲取的表達式的值天然就是 newValue 了,因此測試代碼也是經過的。

expect(getter(context, locals)).toEqual('local');//實際上就是$parse('user.name')(context, locals)

這裏要表現的實際上是上下文的替換功能。

在 getter 的方法中咱們不只能夠選擇第一個上下文,可是若是咱們傳遞了第二個參數,那麼第一個上下文就會被第二個上下文覆蓋,注意是覆蓋.

  1. 獲取當前的表達式 user.name
  2. 獲取當前的上下文對象{user:{name:'angular'}}
  3. 覆蓋當前的上下文{user:{name:'local'}}
  4. 獲取解析以後表達式的值

從新回到 $eval 這個地方,咱們看待 $eval 源碼中能夠看出$eval 只有 get 功能,而沒有 set 功能,可是有些時候咱們能夠選擇傳遞第二個上下文,來達到修改值得效果。

在這裏 $parse 服務就已將說完了,接下來就是 $compile

若是你瞭解了 $parse 的概念以後,我想 $compile 也差很少理解了,其實和 $parse 很像。可是他是解析一段 html 代碼的,他的功能就是將死模板變成活模板,也是指令的核心服務。

好比你有一段 html 代碼 <h1>{{test}}</h1>,若是你將這段代碼直接放在 html 代碼裏面,它所呈現的內容是怎樣的我不說你們也應該懂。這就是死模板了,而所謂的活模板,就是這裏面的數據所有通過了數據的綁定 {{test}}會自動找到當前的上下文,來綁定數據。最後顯示出來的 就是活模板,也就是通過數據綁定的模板。

$compile('死模板')(上下文對象),這樣就將死模板編程了活模板,你就能夠對這段活的 html 代碼作操做了,例如增長到當前節點,等等。

可是在指令中,她會返回兩個函數 pre-linkpost-link

第一個執行的是 pre-link,它對於同一個指令的遍歷順序是從父節點到子節點的遍歷,在這個階段,dom 節點尚未穩定下來,沒法作一些綁定事件的操做,可是咱們能夠在這裏進行一些初始化數據的處理。

第二個執行的是 post-link,也就是咱們常說的 link 函數,他是從子節點到父節點遍歷的,在這個階段,DOM 節點已經穩定下來了,咱們通常會在這裏進行不少的操做。

相關文章
相關標籤/搜索