再談angularJS數據綁定機制及背后原理—angularJS常見問題總結

Angular 的數據綁定采用什么機制,詳述原理?

臟檢查機制。闡釋臟檢查機制,必須先了解如下問題。

單向綁定(ng-bind) 和 雙向綁定(ng-model) 的區別?

ng-bind 單向數據綁定($scope -> view),用于數據顯示,簡寫形式是 {{}}。

兩者的區別在于頁面沒有加載完畢 {{val}} 會直接顯示到頁面,直到 Angular 渲染該綁定數據(這種行為有可能將 {{val}} 讓用戶看到);而 ng-bind 則是在 Angular 渲染完畢后將數據顯示。

ng-model 是雙向數據綁定($scope -> view and view -> $scope),用于綁定值會變化的表單元素等。

雙向數據綁定是 AngularJS 的核心機制之一。當 view 中有任何數據變化時,會更新到 model ,當 model 中數據有變化時,view 也會同步更新,顯然,這需要一個監控。

雙向數據綁定的原理?

Angular 在?scope?模型上設置了一個 監聽隊列,用來監聽數據變化并更新?view?。

每次綁定一個東西到?view?上時 AngularJS 就會往?$watch 隊列里插入一條?$watch,用來檢測它監視的?model?里是否有變化的東西。

當你寫下表達式如{{ val }}時,AngularJS在幕后會為你在scope模型上設置一個watcher(表達式將被 Angular 編譯成一個監視函數),它用來在數據發生變化的時候更新view。這里的watcher和你會在AngularJS中設置的watcher是一樣的:

$scope.$watch('val',?function(newValue,?oldValue)?{

??//update?the?DOM?with?newValue

});

將數據附加到 Scope 上,數據自身不會對性能產生影響,如果沒有監視器來監視這個屬性,那個這個屬性在不在 Scope 上是無關重要的;Angular 并不會遍歷 Scope 上的屬性,它將遍歷所有的觀察器。

每個監視函數是在每次 $digest 過程中被調用的。因此,我們要注意觀察器的數量以及每個監視函數或者監視表達式的性能。

$digest循環是在什么時候以各種方式開始的?

當瀏覽器接收到可以被 angular context 處理的事件時,$digest 循環就會觸發,遍歷所有的 $watch,最后更新 dom。

舉個栗子

increase?1

click 時會產生一次更新的操作(至少觸發兩次 $digest 循環)

按下按鈕

瀏覽器接收到一個事件,進入到 angular context

$digest?循環開始執行,查詢每個?$watch?是否變化

由于監視?$scope.val 的?$watch?報告了變化,因此強制再執行一次?$digest?循環

新的?$digest?循環未檢測到變化

瀏覽器拿回控制器,更新?$scope.?val.新值對應的 dom

在調用了$scope.$digest()后,$digest循環就開始了。假設你在一個ng-click指令對應的handler函數中更改了scope中的一條數據,此時AngularJS會自動地通過調用$digest()來觸發一輪$digest循環。當$digest循環開始后,它會觸發每個watcher。這些watchers會檢查scope中的當前model值是否和上一次計算得到的model值不同。如果不同,那么對應的回調函數會被執行。調用該函數的結果,就是view中的表達式內容(譯注:諸如{{ val }})會被更新。除了ng-click指令,還有一些其它的built-in指令以及服務來讓你更改models(比如ng-model,$timeout等)和自動觸發一次$digest循環。

目前為止還不錯!但是,有一個小問題。在上面的例子中,AngularJS并不直接調用$digest(),而是調用$scope.$apply(),后者會調用$rootScope.$digest()。因此,一輪$digest循環在$rootScope開始,隨后會訪問到所有的children scope中的watchers。

通常寫代碼時我們無需主動調用 $apply 或 $digest 是因為 angular 在外部對我們的回調函數做了包裝。例如常用的 ng-click,這是一個指令(Directive),內部實現則 類似 于

DOM.addEventListener('click',?function?($scope)?{

??$scope.$apply(()?=>?userCode());

});

可以看到:ng-click 幫我們做了 $apply 這個操作。類似的不只是這些事件回調函數,還有 $http、$timeout 等。我聽很多人抱怨說 angular 這個庫太大了什么都管,其實你可以不用它自帶的這些服務(Service),只要你記得手工調用 $scope.$apply。

現在,假設你將ng-click指令關聯到了一個button上,并傳入了一個function名到ng-click上。當該button被點擊時,AngularJS會將此function包裝到一個wrapping function中,然后傳入到$scope.$apply()。因此,你的function會正常被執行,修改models(如果需要的話),此時一輪$digest循環也會被觸發,用來確保view也會被更新。

Note: $scope.$apply()會自動地調用$rootScope.$digest()。$apply()方法有兩種形式。第一種會接受一個function作為參數,執行該function并且觸發一輪$digest循環。第二種會不接受任何參數,只是觸發一輪$digest循環。我們馬上會看到為什么第一種形式更好。

$digest 循環會運行多少次?

$digest?循環的上限是 10 次(超過 10次后拋出一個異常,防止無限循環)。

$digest 循環不會只運行一次。在當前的一次循環結束后,它會再執行一次循環用來檢查是否有 models 發生了變化。

這就是臟檢查(Dirty Checking),它用來處理在 listener 函數被執行時可能引起的 model 變化。因此 $digest 循環會持續運行直到 model 不再發生變化,或者 $digest 循環的次數達到了 10 次(超過 10 次后拋出一個異常,防止無限循環)。

當 $digest 循環結束時,DOM 相應地變化。

臟檢查如何被觸發?

angular 會在可能觸發 UI 變更的時候進行臟檢查:這句話并不準確。實際上,

臟檢查是digest執行的,另一個更常用的用于觸發臟檢查的函數apply——其實就是 $digest 的一個簡單封裝(還做了一些抓異常的工作)。

通常寫代碼時我們無需主動調用 $apply 或 $digest 是因為 angular 在外部對我們的回調函數做了包裝。例如常用的 ng-click,這是一個指令(Directive),內部實現則 類似于

DOM.addEventListener('click',?function?($scope)?{

??$scope.$apply(()?=>?userCode());

});

angular對常用的dom事件,xhq事件作了封裝,如果調用這些封裝,就會在里面觸發進入angular的digest流程,主要有以下情況:

DOM事件,如用戶輸入文本,點擊按鈕等,(ng-click)

XHQ響應事件($http)

瀏覽器Location變更事件,即Url中hash部分變更($location)

Timer事件($Timeout,$interval)

手動調用$apply或$digest

$apply() 和$digest() 的區別?

$apply 是 $scope(或者是 direcvie 里的 link 函數中的 scope)的一個函數,調用它會強制一次 $digest 循環(除非當前正在執行循環,這種情況下會拋出一個異常,這是我們不需要在那里執行 $apply 的標志)。

$apply() 和 $digest() 有兩個區別。

1) 最直接的差異是, $apply 可以帶參數,它可以接受一個函數,然后在應用數據之后,調用這個函數。所以,一般在集成非 Angular 框架(比如jQuery)的代碼時,可以把代碼寫在這個里面調用。

2) 當調用 $digest 的時候,只觸發當前作用域和它的子作用域上的監控,但是當調用 $apply 的時候,會觸發作用域樹上的所有監控。

什么時候手動調用 $apply() 方法?

取決于是否在 Angular 上下文環境(angular context)。

AngularJS對此有著非常明確的要求,就是它只負責對發生于AngularJS上下文環境中的變更會做出自動地響應(即,在$apply()方法中發生的對于models的更改)。AngularJS的built-in指令就是這樣做的,所以任何的model變更都會被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通過手動調用$apply()來通知AngularJS。這就像告訴AngularJS,你修改了一些models,希望AngularJS幫你觸發watchers來做出正確的響應。

典型的需要調用 $apply() 方法的場景是:

1) 使用了 JavaScript 中的 setTimeout() 來更新一個 scope model

2) 用指令設置一個 DOM 事件 listener 并且在該 listener 中修改了一些 models

場景一

$scope.setMsg?=?function()?{

????setTimeout(function()?{

????????$scope.message?=?'hello?world';

????????console.log('message:'?+?$scope.message);

????},?2000);

}

$scope.setMsg();

運行這個例子,會看到過了兩秒鐘之后,控制臺確實會顯示出已經更新的 model,然而,view 并沒有更新。

在 $scope.getMessage 加入 $apply() 方法。

$scope.getMessage?=?function()?{

????setTimeout(function()?{

????????$scope.$apply(function()?{

????????????$scope.message?=?'hello?world';

????????????console.log('message:'?+?$scope.message);

????????});

????},?2000);

}

再運行就 OK 了。

不過,在 AngularJS 中應該盡量使用 $timeout Service 來代替 setTimeout(),因為前者會幫你調用 $apply(),讓你不需要手動地調用它。

場景二

實現一個 click 的指令,類似以下功能,directive 的編寫如下:

app.directive("inc",?function()?{

????return?function?(scope,?element,?attr)?{

????????element.on("click",?function()?{

????????????scope.val++;

????????});

????};

});

跟場景一的結果一樣,這個時候,點擊按鈕,界面上的數字并不會增加。但查看調試器,發現數據確實已經增加了。

在 scope.val++; 一行后面添加 scope.$apply(); 或者 scope.$digest(); 就 OK 了。

$apply() 方法的兩種形式

//無參

$scope.$apply()

//有參

$scope.$apply(function(){

????...

})

應該總使用接受一個 function 作為參數的 $apply() 方法。這是因為當傳入一個 function 到 $apply() 中的時候,這個 function 會被包裝到一個 try…catch 塊中,所以一旦有異常發生,該異常會被 $exceptionHandler service 處理。

想象一下如果有個 alert 框顯示錯誤給用戶,然后有個第三方的庫進行一個網絡調用然后失敗了,如果不把它封裝進 $apply 里面,Angular 永遠不會知道失敗了,alert 框就永遠不會彈出來了。

在 AngularJS 中使用 $watch注意事項?

如果要監聽的是一個對象,那還需要第三個參數

$scope.data.name?=?'htf';

$scope.$watch('data',?function(newValue,?oldValue)?{

????if?(newValue?===?oldValue)?{?return;?}

????$scope.updated++;

},?true);

表示比較的是對象的值而不是引用,如果不加第三個參數 true ,在 data.name 變化時,不會觸發相應操作,因為引用的是同一引用。

臟檢查的范圍

前面說到:angular 會對所有綁定到 UI 上的表達式做臟檢查。其實,在 angular 實現內部,所有綁定表達式都被轉換為?$scope.$watch()。每個?$watch?記錄了上一次表達式的值。有?ng-bind="a"?即有?$scope.$watch('a', callback),而?$scope.$watch?可不會管被?watch?的表達式是否跟觸發臟檢查的事件有關。

例如:

??

TEST

問:點擊 TEST 這個按鈕時會觸發臟檢查嗎?觸發幾次?

首先:ng-click="" 什么都沒有做。angular 會因為這個事件回調函數什么都沒做就不進行臟檢查嗎?不會。

然后:#span1 被隱藏掉了,會檢查綁定在它上面的表達式嗎?盡管用戶看不到,但是 $scope.$watch('content', callback) 還在。就算你直接把這個 span 元素干掉,只要 watch 表達式還在,要檢查的還會檢查。

再次:重復的表達式會重復檢查嗎?會。

最后:別忘了 ng-show="false"??赡苁且驗?angular 的開發人員認為這種綁定常量的情況并不多見,所以 $watch 并沒有識別所監視的表達式是否是常量。常量依舊會重復檢查。

所以:

答:觸發三次。一次 false,一次 content,一次 content

所以說一個綁定表達式只要放在當前 DOM 樹里就會被監視,不管它是否可見,不管它是否被放在另一個 Tab 里,更不管它是否與用戶操作相關。

另外,就算在不同 Controller 里構造的 $scope 也會互相影響,別忘了 angular 還有全局的 $rootScope,你還可以 $scope.$emit。angular 無法保證你絕對不會在一個 controller 里更改另一個 controller 生成的 scope,包括 自定義指令(Directive)生成的 scope 和 Angular 1.5 里新引入的組件(Component)。

所以說不要懷疑用戶在輸入表單時 angular 會不會監聽頁面左邊導航欄的變化。

如何優化臟檢查與運行效率

臟檢查慢嗎?

說實話臟檢查效率是不高,但是也談不上有多慢。簡單的數字或字符串比較能有多慢呢?十幾個表達式的臟檢查可以直接忽略不計;上百個也可以接受;成百上千個就有很大問題了。綁定大量表達式時請注意所綁定的表達式效率。建議注意一下幾點:

表達式(以及表達式所調用的函數)中少寫太過復雜的邏輯

不要連接太長的 filter(往往 filter 里都會遍歷并且生成新數組)

不要訪問 DOM 元素。

1、使用單次綁定減少綁定表達式數量單次綁定(One-time binding 是 Angular 1.3 就引入的一種特殊的表達式,它以 :: 開頭,當臟檢查發現這種表達式的值不為 undefined 時就認為此表達式已經穩定,并取消對此表達式的監視。這是一種行之有效的減少綁定表達式數量的方法,與 ng-repeat 連用效果更佳(下文會提到),但過度使用也容易引發 bug。

2、善用 ng-if 減少綁定表達式的數量

如果你認為 ng-if 就是另一種用于隱藏、顯示 DOM 元素的方法你就大錯特錯了。

ng-if 不僅可以減少 DOM 樹中元素的數量(而非像 ng-hide 那樣僅僅只是加個 display: none),每一個 ng-if 擁有自己的 scope,ng-if 下面的 $watch 表達式都是注冊在 ng-if 自己 scope 中。當 ng-if 變為 false,ng-if 下的 scope 被銷毀,注冊在這個 scope 里的綁定表達式也就隨之銷毀了。

考慮這種 Tab 選項卡實現:

    ??Tab?1?title

    ??Tab?2?title

    ??Tab?3?title

    ??Tab?4?title

    [[Tab?1?body...]]

    [[Tab?2?body...]]

    [[Tab?3?body...]]

    [[Tab?4?body...]]

    對于這種會反復隱藏、顯示的元素,通常人們第一反應都是使用 ng-show 或 ng-hide 簡單的用 display: none 把元素設置為不可見。

    然而入上文所說,肉眼不可見不代表不會跑臟檢查。如果將 ng-show 替換為 ng-if 或 ng-switch-when

    [[Tab?1?body...]]

    [[Tab?2?body...]]

    [[Tab?3?body...]]

    [[Tab?4?body...]]

    有如下優點:

    首先 DOM 樹中的元素個數顯著減少至四分之一,降低內存占用

    其次 $watch 表達式也減少至四分之一,提升臟檢查循環的速度

    如果這個 tab 下面有 controller(例如每個 tab 都被封裝為一個組件),那么僅當這個 tab 被選中時該 controller 才會執行,可以減少各頁面的互相干擾

    如果 controller 中調用接口獲取數據,那么僅當對應 tab 被選中時才會加載,避免網絡擁擠

    當然也有缺點:

    DOM 重建本身費時間

    如果 tab 下有 controller,那么每次該 tab 被選中時 controller 都會被執行

    如果在 controller 里面調接口獲取數據,那么每次該 tab 被選中時都會重新加載

    各位讀者自己取舍。

    3、給 ng-repeat 手工添加 track by

    不恰當的 ng-repeat 會造成 DOM 樹反復重新構造,拖慢瀏覽器響應速度,造成頁面閃爍。除了上面這種比較極端的情況,如果一個列表頻繁拉取 Server 端數據自刷新的話也一定要手工添加 track by,因為接口給前端的數據是不可能包含 $$hashKey 這種東西的,于是結果就造成列表頻繁的重建。

    其實不必考慮那么多,總之加上沒壞處,至少可以避免 angular 生成 $$hashKey 這種奇奇怪怪的東西。

    具體參看:詳解track by

    臟檢測的利弊?

    很多人對Angular的臟檢測機制感到不屑,推崇基于setter,getter的觀測機制,在我看來,這只是同一個事情的不同實現方式,并沒有誰完全勝過誰,兩者是各有優劣的。

    大家都知道,在循環中批量添加DOM元素的時候,會推薦使用DocumentFragment,為什么呢,因為如果每次都對DOM產生變更,它都要修改DOM樹的結構,性能影響大,如果我們能先在文檔碎片中把DOM結構創建好,然后整體添加到主文檔中,這個DOM樹的變更就會一次完成,性能會提高很多。

    同理,在Angular框架里,考慮到這樣的場景:

    function?TestCtrl($scope)?{

    ????$scope.numOfCheckedItems?=?0;

    ????var?list?=?[];

    ????for?(var?i=0;?i<10000;?i++)?{

    ????????list.push({

    ????????????index:?i,

    ????????????checked:?false

    ????????});

    ????}

    ????$scope.list?=?list;

    ????$scope.toggleChecked?=?function(flag)?{

    ????????for?(var?i=0;?i

    ????????????list[i].checked?=?flag;

    ????????????$scope.numOfCheckedItems++;

    ????????}

    ????};

    }

    如果界面上某個文本綁定這個numOfCheckedItems,會怎樣?在臟檢測的機制下,這個過程毫無壓力,一次做完所有數據變更,然后整體應用到界面上。這時候,基于setter的機制就慘了,除非它也是像Angular這樣把批量操作延時到一次更新,否則性能會更低。

    所以說,兩種不同的監控方式,各有其優缺點,最好的辦法是了解各自使用方式的差異,考慮出它們性能的差異所在,在不同的業務場景中,避開最容易造成性能瓶頸的用法。

    ng-if跟ng-show/hide的區別有哪些?

    第一點區別是,ng-if 在后面表達式為 true 的時候才創建這個 dom 節點,ng-show 是初始時就創建了,用 display:block 和 display:none 來控制顯示和不顯示。第二點區別是,ng-if 會(隱式地)產生新作用域,ng-switch 、 ng-include 等會動態創建一塊界面的也是如此。

    ng-repeat迭代數組的時候,如果數組中有相同值,會有什么問題,如何解決?

    會提示 Duplicates in a repeater are not allowed. 加 track by $index 可解決。當然,也可以 trace by 任何一個普通的值,只要能唯一性標識數組中的每一項即可(建立 dom 和數據之間的關聯)。

    ng-click中寫的表達式,能使用JS原生對象上的方法,比如Math.max之類的嗎?為什么?

    不可以。只要是在頁面中,就不能直接調用原生的 JS 方法,因為這些并不存在于與頁面對應的 Controller 的 $scope 中。除非在 $scope 中添加了這個函數:

    $scope.parseInt?=?function(x){

    ????return?parseInt(x);

    }

    {{now | 'yyyy-MM-dd'}}這種表達式里面,豎線和后面的參數通過什么方式可以自定義?

    定義方式:

    app.filter('過濾器名稱',function(){

    ????return?function(需要過濾的對象,?過濾器參數1,?過濾器參數2,?...){

    ????????//...做一些事情

    ????????return?處理后的對象;

    ????}

    });

    使用方式有兩種,一種是直接在頁面里:

    {{now?|?date?:?'yyyy-MM-dd'}}

    一種是在 js 里面用:

    //?$filter('過濾器名稱')(需要過濾的對象,?參數1,?參數2,...)

    $filter('date')(now,?'yyyy-MM-dd?hh:mm:ss');

    ?factory和service,provider是什么關系?

    factory 把 service 的方法和數據放在一個對象里,并返回這個對象;service 通過構造函數方式創建 service,返回一個實例化對象;provider 創建一個可通過 config 配置的 service。從底層實現上來看,service 調用了 factory,返回其實例;factory 調用了 provider,將其定義的內容放在 $get 中返回。factory 和 service 功能類似,只不過 factory 是普通 function,可以返回任何東西(return 的都可以被訪問,所以那些私有變量怎么寫你懂的);service 是構造器,可以不返回(綁定到 this 的都可以被訪問);provider 是加強版 factory,返回一個可配置的 factory。

    詳述angular的“依賴注入”

    AngularJS 是通過構造函數的參數名字來推斷依賴服務名稱的,通過 toString() 來找到這個定義的 function 對應的字符串,然后用正則解析出其中的參數(依賴項),再去依賴映射中取到對應的依賴,實例化之后傳入。因為 AngularJS 的 injector 是假設函數的參數名就是依賴的名字,然后去查找依賴項,那如果像下面這樣簡單注入依賴,代碼壓縮后(參數被重命名了),就無法查找到依賴項了。

    function?myCtrl?=?($scope,?$http){

    ????...}

    所以,通常會使用下面兩種方式注入依賴(對依賴添加的順序有要求)。

    數組注釋法:

    myApp.controller('myCtrl',?['$scope',?'$http',?function($scope,?$http){

    ????...

    }])

    顯式 $inject :

    myApp.controller('myCtrl',?myCtrl);

    function?myCtrl?=?($scope,?$http){

    ????...

    }

    myCtrl.$inject?=?['$scope',?'$http'];

    對于一個 DI 容器,必須具備三個要素:依賴項的注冊,依賴關系的聲明和對象的獲取。在 AngularJS 中,module 和 $provide 都可以提供依賴項的注冊;內置的 injector 可以獲取對象(自動完成依賴注入);依賴關系的聲明,就是上面的那兩種方式。

    html: {{currentDate()}} js: $scope.currentDate = function(){return new Date();} 這種寫法有沒有問題

    有問題,時間是實時變化的,然后會一直更新數據,效率低,臟數據檢查到10次之后不再繼續檢查;

    解決方案:可以使用一個變量來接收函數調用

    controller as 和controller 有什么區別,能解決什么問題?

    在使用controller的時候,為控制器注入$window與$scope,這個時候controller中的屬性與方法是屬于$scope的,而使用controllerAS的時候,可以將controller定義為Javascript的原型類,在html中直接綁定原型類的屬性和方法

    優點:

    可以使用 Javascript 的原型類, 我們可以使用更加高級的 ES6 或者 TypeScript 來編寫 Controller ;

    指代清晰。在嵌套scope時,子scope如果想使用父scope的屬性,只需簡單的使用父scope的別名引用父scope即可。

    避開了所謂的 child scope 原型繼承帶來的一些問題(原來別名ctrl就是定義在$scope上的一個對象,這就是controller的一個實例,所有在JS中定義controller時綁定到this上的model其實都是綁定到$scope.ctrl上的。使用controller as的一大好處就是原型鏈繼承給scope帶來的問題都不復存在了,即有效避免了在嵌套scope的情況下子scope的屬性隱藏掉父scope屬性的情況。)

    controller的定義不依賴$scope。

    定義controller時不用顯式的依賴$scope,這有什么好處呢?仔細看定義,這不就是一個普通的函數定義嘛,對!這就是好處!例子中的ScopeController就是所謂的POJO(Plain Old Javascript Object,Java里偷來的概念),這樣的Object與框架無關,里面只有邏輯。所以即便有一天你的項目不再使用AngularJS了,依然可以很方便的重用和移植這些邏輯。另外,從測試的角度看,這樣的Object也是單元測試友好的。單元測試強調的就是孤立其他依賴元素,而POJO恰恰滿足這個條件,可以單純的去測試這個函數的輸入輸出,而不用費勁的去模擬一個假的$scope。

    防止濫用$scope的$watch,$on,$broadcast方法??赡軇倓偩陀腥讼雴柫?,不依賴$scope我怎么watch一個model,怎樣廣播和響應事件。答案是沒法弄,這些事還真是只有$scope能干。但很多時候在controller里watch一個model是很多余的,這樣做會明顯的降低性能。所以,當你本來就依賴$scope的時候,你會習慣性的調用這些方法來實現自己的邏輯。但當使用controller as的時候,由于沒有直接依賴$scope,使用watch前你會稍加斟酌,沒準就思考到了別的實現方式了呢。

    定義route時也能用controller as。除了在DOM中顯式的指明ng-controller,還有一種情況是controller的綁定是route里定義好的,那這時能使用controller as嗎?答案是肯定的,route提供了一個controllerAs參數。這樣在模板里就可以直接使用別名home啦。

    個人覺得還是偏向于使用controller as的,當然有一點要澄清,使用contoller as并沒有什么性能上的提升,僅僅是一種好的習慣罷了。

    無論定義controller時有沒有直接依賴$scope,DOM中的scope是始終存在的。即使使用controller as,雙向綁定還是通過$scope的watch以及digest來實現的。

    請簡述$compile的用法?

    angularjs里比較重要但又很少手動調用的要屬$compile服務了,通常在寫組件或指令時,都是angularjs自動編譯完成的,但有時我們可能需要手動編譯,比如封裝一個table組件,根據參數實現自定義渲染,增加一列復選框或者一列按鈕啥的,這是就需要用到$compile了。

    $compile,在Angular中即“編譯”服務,它涉及到Angular應用的“編譯”和“鏈接”兩個階段,根據從DOM樹遍歷Angular的根節點(ng-app)和已構造完畢的 \$rootScope對象,依次解析根節點后代,根據多種條件查找指令,并完成每個指令相關的操作(如指令的作用域,控制器綁定以及transclude等),最終返回每個指令的鏈接函數,并將所有指令的鏈接函數合成為一個處理后的鏈接函數,返回給Angluar的bootstrap模塊,最終啟動整個應用程序。

    先解說下angular中頁面處理

    ng對頁面的處理過程:

    瀏覽器把HTML字符串解析成DOM結構

    ng把DOM結構給$compile,返回一個link函數

    傳入具體的scope調用這個link函數

    得到處理后的DOM,這個DOM處理了指令,連接了數

    $compile是個編譯服務。編譯服務主要是為指令編譯DOM元素。

    編譯一段HTML字符串或者DOM的模板,產生一個將scope和模板連接到一起的函數。

    $compile用法:

    $compile(element,transclude,maxPriority);

    element:將要被編譯和插入模板的元素或者HTML字符串

    transclude:指令內有效的函數。Function(angular.Scope,cloneAttachFn=)

    maxPriority:只有在指令比給定的優先級低時應用。只影響根元素,不影響子元素

    ?.controller('MyController',?function?($scope,?$compile)?{

    ????????????????//?創建編譯函數

    ????????????????var?compileFn?=?$compile('

    {{appCtrl.msg}}

    ');

    ????????????????//?傳入scope,得到編譯好的dom對象(已封裝為jqlite對象)

    ????????????????//?也可以用$scope.$new()創建繼承的作用域

    ????????????????var?$dom?=?compileFn($scope);

    ????????????????//?添加到文檔中

    ????????????????$dom.appendTo('body');

    ????????????})

    通過$compile服務可以編譯html字符串或dom對象或jqLite對象,然后得到一個編譯函數,再傳入$scope,就會在當前作用域進行編譯,返回編譯好的jqLite對象,這時就可以直接添加到文檔中了(也可以先添加到文檔再編譯)。

    編譯的實質其實就是對dom對象解析,使dom對象與scope進行耦合,通過綁定可以實現數據的更新,像Vue其實也是一樣的過程。

    $compile解說推薦看《Angular中$compile源碼分析

    這篇是對angularJS的一些疑點回顧,文章的問題大多是從網上搜集整理而來,如有不妥之處或不遠被引用,請通知本人修改,謝謝!

    首發于周陸軍個人網站:https://www.zhoulujun.cn/html/webfront/ECMAScript/angularjs/2018_0417_8097.html

    轉載注明來源,謝謝!

    參考文章:

    溫故而知新-AngularJS 1.x 小記(angularJS各個模塊系統概述)

    關于 AngularJS 的數據綁定黃騰飛的個人網站

    AngularJS 臟檢查深入分析

    理解Angular中的$apply()以及$digest()(翻譯:原文地址

    MVVM的簡單實現-臟檢測

    Angular系列(徐飛博客:由淺入深地闡釋了angularJS)

    Angular 1 深度解析:臟數據檢查與 angular 性能優化

    用$scope還是用controller as

    Controller As與$scope的區別($scope篇)

    《用AngularJS開發下一代Web應用》讀書筆記②:AngularJS應用骨架

    AngularJs $compile編譯服務與指令

    玩轉Angular1(14)--使用$compile編譯指令

    Angular中$compile源碼分析

    angularjs使用$compile編譯模板后如何獲取編譯后的模板內容并將其轉成字符串

    ?著作權歸作者所有,轉載或內容合作請聯系作者
    平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
    • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
      沈念sama閱讀 230,106評論 6 542
    • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
      沈念sama閱讀 99,441評論 3 429
    • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
      開封第一講書人閱讀 178,211評論 0 383
    • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
      開封第一講書人閱讀 63,736評論 1 317
    • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
      茶點故事閱讀 72,475評論 6 412
    • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
      開封第一講書人閱讀 55,834評論 1 328
    • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
      沈念sama閱讀 43,829評論 3 446
    • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
      開封第一講書人閱讀 43,009評論 0 290
    • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
      沈念sama閱讀 49,559評論 1 335
    • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
      茶點故事閱讀 41,306評論 3 358
    • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
      茶點故事閱讀 43,516評論 1 374
    • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
      沈念sama閱讀 39,038評論 5 363
    • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
      茶點故事閱讀 44,728評論 3 348
    • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
      開封第一講書人閱讀 35,132評論 0 28
    • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
      開封第一講書人閱讀 36,443評論 1 295
    • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
      沈念sama閱讀 52,249評論 3 399
    • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
      茶點故事閱讀 48,484評論 2 379

    推薦閱讀更多精彩內容