angularjs link compile與controller的區別詳解,瞭解angular生命週期

 壹 ❀ 引html

我在 angularjs 一篇文章看懂自定義指令directive 一文中簡單說起了自定義指令中的link連接函數與compile編譯函數,並說到二者具備互斥特性,即同時存在link與compile時link不生效。因爲上篇博文篇幅問題,實在很差再過多討論link,compile,那麼本文將圍繞三個問題展開,一是再識link與compile函數,你將知道二者爲什麼互斥;二是瞭解link、compile與controller的區別,存在即合理,在合適的場景下應該使用哪一個方法;三是瞭解指令中代碼執行順序,link與controller執行關係,多層指令又會如何執行?那麼本文開始。angularjs

 貳 ❀ directive中的link與compile安全

咱們已經知道編譯函數compile與連接函數link互斥,兩者只能存在其一,好比下方例子中,link函數並不會執行:dom

angular.module('myApp', [])
    .controller('myCtrl', function ($scope, $q) {}).directive("echo", function () {
        return {
            restrict: 'EA',
            compile: function () {
                console.log('開始編譯了!');
            },
            link: function () {
                console.log('開始給DOM綁定事件數據了!')//不執行
            }
        }
    })

那這樣就產生了一個問題,是否是compile存在就不能操做link函數了?並非這樣,完整的compile函數其實自己就包含了link函數,有以下兩種寫法:函數

寫法一:post

angular.module('myApp', [])
    .controller('myCtrl', function () {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            scope: {},
            replace: true,
            controller: function ($scope, $element) {},
            compile: function (tEle, tAttrs, transcludeFn) {
                //這裏模板編譯完成但還沒被成功返回,咱們能夠對編譯後的DOM樹加工
                console.log('編譯完成,加工DOM吧')
                //返回一個函數做爲link函數,模板編譯已完成,進入連接階段
                return function postLink(scope, ele, attrs) {
                    console.log('開始執行連接函數link');
                };
            }
        }
    })

寫法二:ui

angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            scope: {},
            controller: function ($scope, $element) {},
            compile: function () {
                //這裏模板編譯完成但還沒被成功返回,咱們能夠對編譯後的DOM樹加工
                console.log('模板編譯完成,能夠訪問使用指令的dom元素以及元素上的屬性了')

                //返回一個對象做爲link函數,只是這個link又分爲了兩個部分
                return {
                    pre: function (scope, iElement, iAttrs, controller) {
                        //在子元素被連接以前執行(也就是子元素的postLink執行以前),這裏執行DOM轉換和連接函數不安全
                        console.log('pre開始執行了');
                    },
                    post: function (scope, iElement, iAttrs, controller) {
                        // 在子元素被連接以後執行,在這裏執行DOM轉換和連接函數同樣安全
                        console.log('link開始執行了');
                    }
                }
            }
        }
    })

在上面這個例子中,compile返回的整個對象做爲link函數,只是link函數又分爲了pre與post兩個階段,我暫且稱爲preLinkpostLink函數,其中postLink對應的就是咱們熟悉的Link函數this

postLink咱們知道是在DOM元素連接階段完成以後執行,而preLink有點特殊,它是在全部指令模板編譯完成且子指令postLink執行以前執行(也就是子指令連接階段以前),雖然preLink函數中也能給指令模板綁定數據方法,但通常不推薦使用preLink函數,要麼使用postLink,或者不寫compile直接使用Link函數。spa

我知道你這裏必定有疑問了,preLink和postLink都能給指令綁定事件監聽DOM,官方爲啥不推薦使用preLink,沒用設計它幹嗎,兩者真就一點區別也沒有?固然有,這個咱們得先介紹angular的生命週期,不瞭解這個還真很差解釋。設計

 肆 ❀ angular生命週期

經過上文的compile與link瞭解,咱們大體知道了angular生命週期中存在編譯階段與連接階段兩個重要階段。angular的指令在angular啓動前,會以普通文本形式保存在HTML中,但當angular正式啓動,這些指令就會經歷編譯與連接。

1.編譯階段

在編譯階段angular會找到指令,若指令存在模板則開始編譯解析模板,但有個問題,指令模板中也可能存在模板,因而還得編譯指令模板中子指令的模板,相似與深度遍歷。

一旦指令DOM編譯完成,模板就會返回一個模板函數,咱們有機會在指令的模板函數被返回前對編譯後的DOM樹進行修改,這個機會就在咱們前面說的compile函數裏。

直到令和子指令模板DOM編譯完成,最外層的父指令模板會統一返回一個模板函數,待模板函數返回完成,編譯階段正式結束。

因爲compile處於DOM解析完成且模板函數還未成功返回的階段,因此compile函數執行必定與編譯順序保持一致,知足從上到下,從外到內的前後順序執行,咱們來看例子:

<body ng-controller="myCtrl">
    <div echo1></div>
    <div echo3></div>
</body>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo1', function () {
        return {
            restrict: 'EACM',
            template:'<span><echo2></echo2></span>',
            compile: function () {
                console.log('compile1開始執行');
            }
        }
    })
    .directive('echo2', function () {
        return {
            restrict: 'EACM',
            compile: function () {
                console.log('compile2開始執行');
            }
        }
    })
    .directive('echo3', function () {
        return {
            restrict: 'EACM',
            compile: function () {
                console.log('compile3開始執行');
            }
        }
    })

上述例子中,指令echo1擁有子指令echo2與兄弟DOM指令echo3,直到echo2編譯完成,echo3才能編譯,那麼咱們知道compile執行與編譯階段保持一致,知足從上到下,從父到子深度遍歷的順序

因爲compile能夠對編譯出來的DOM進行再加工,因此最終編譯出來的DOM樹可能與你模板中的DOM結構不一致,所以不推薦在compile階段作監聽DOM事件的操做。

2.連接階段

在compile執行結束,模板函數被返回並傳遞給了指令中定義的link函數,此時開始連接階段;連接階段負責將編譯階段編譯好的DOM樹與scope相關聯,這樣link函數就能將定義好的數據,事件與DOM綁定在一塊兒,實現DOM操做與監聽。

前面也說了指令也會有子指令,而這個preLink則在編譯完成(compile)以後子指令連接以前(preLink)執行,因此preLink也在compile以後,且在子指令的preLink與postLink以前執行。

postLink比較特殊,postLink永遠在編譯完成且子指令連接以後執行(postLink以後),因此也是在compile以後,且在子指令的postLink以後。

有點混亂了,理一理,以單個指令來講,它應該是編譯階段開始---DOM編譯成功執行compile---返回模板函數(編譯結束)---模板函數傳遞給link---連接階段開始,DOM與scope關聯---執行pre---執行post---連接階段結束

而當指令包含子指令時,它應該是編譯階段開始---父指令DOM編譯成功執行父compile---返回模板函數---子指令DOM編譯成功執行子compile---返回模板函數---模板函數傳遞給link---連接階段開始,DOM與scope關聯---執行父pre---執行子pre---執行子post---執行父post---連接階段結束

看個例子:

<body ng-controller="myCtrl">
    <div echo></div>
</body>
angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            template:'<span><echo1></echo1></span>',
            compile: function () {
                console.log('compile1開始執行');
                return {
                    pre: function () {
                        console.log('pre1開始執行');
                    },
                    post: function () {
                        console.log('post1開始執行');
                    }
                }
            }
        }
    })
    .directive('echo1', function () {
        return {
            restrict: 'EACM',
            compile: function () {
                console.log('compile2開始執行');
                return {
                    pre: function () {
                        console.log('pre2開始執行');
                    },
                    post: function () {
                        console.log('post2開始執行');
                    }
                }
            }
        }
    })

compile與pre就像深度遍歷,有子就一直往下執行,post就像回溯,從裏往外執行。

3.preLink與postLink的區別

前面咱們留下了一個問題,preLink到底有什麼用,咱們來看下面這段代碼,猜猜會如何執行:

angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            template: '<span><echo1></echo1></span>',
            link: function (scope) {
                scope.name = '聽風是風';
            }
        }
    })
    .directive('echo1', function () {
        return {
            restrict: 'EACM',
            template: '<span>{{describe}}</span>',
            link: function (scope) {
                scope.describe = '個人名字是' + scope.name;
            }
        }
    })

致使scope.name沒法取到值的緣由是,這裏的link函數就是咱們以前提到的postLink函數,postLink函數執行就像回溯,子指令先執行,因此取值的時候父指令還未聲明此變量。想要作到父指令給子指令做用域傳值,preLink就能作到這一點:

angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            template: '<span><echo1></echo1></span>',
            compile: function () {
                return {
                    pre: function (scope) {
                        scope.name = '聽風是風';
                    },
                    post: function () {

                    }
                }
            }
        }
    })
    .directive('echo1', function () {
        return {
            restrict: 'EACM',
            template: '<span>{{describe}}</span>',
            link: function (scope) {
                scope.describe = '個人名字是' + scope.name;
            }
        }
    })

那麼到這裏咱們知道了pre與post的區別,pre能夠利用本身執行順序的優點給子指令做用域直接傳值,可是仍然不推薦這麼作,這裏咱們只是做爲知識瞭解。畢竟指令應該擁有乾淨隔離的做用域,也不會用到這種傳值模式。

 叄 ❀ link、compile、controller的職責

那麼經過上文的介紹,咱們知道了link與compile對應了連接和編譯兩個階段,編譯函數負責對模板DOM進行轉換,在做用域同DOM連接以前能夠手動操做DOM。在開發中編寫自定義指令時這種操做是很是罕見,因此compile使用很少。

連接函數負責將做用域和DOM進行連接,編譯函數會在模板編譯完成並同做用域進行連接後被調用,所以它負責設置事件監聽器,監視數據變化和實時的更新DOM,這與controller十分相似。

拋開加工DOM的compile不說,那咱們應該在什麼狀況下使用controller和link呢,畢竟這兩兄弟都能作DOM事件監聽與數據更新;其實很簡單,若是你但願這個指令的屬性方法能被其它指令複用,那就將方法屬性定義在controller中,若是隻是但願給指令本身使用,那就加在link函數中。

之因此這麼說,是由於指令有一個require屬性,經過require,咱們能將require值同名指令的controller加入到當前指令中,而後就能夠經過link函數的第四個參數直接使用被require指令controller中的屬性方法了,來看個例子:

angular.module('myApp', [])
    .controller('myCtrl', function ($scope) {})
    .directive('echo', function () {
        return {
            restrict: 'EACM',
            template: '<span><echo1></echo1></span>',
            controller: function () {
                this.sayName = function (name) {
                    console.log('個人名字是' + name);
                }
            }
        }
    })
    .directive('echo1', function () {
        return {
            restrict: 'EACM',
            template: '<button ng-click="myName(name)">點我</button>',
            require: '^echo',
            link: function (scope, ele, attr, ctrl) {
                console.log(ctrl);
                scope.name = '聽風是風';
                scope.myName = ctrl.sayName;
            }
        }
    })

能夠看到在指令echo1中成功注入了指令echo的controller,咱們能在echo1中直接使用echo的方法,這就是爲什麼說若是指令方法須要複用,建議綁在controller中的緣由。

 肆 ❀ 總

那麼到這裏,咱們知道了compile與link是互斥關係,若是同時寫了兩個函數,link不會執行,這是由於compile自己就會返回一個函數做爲link,哪怕你不寫返回函數,那也認定你返回了一個空的link。

其次,咱們知道了pre與post的區別,這兩個函數雖然同屬於link的一部分,但在生命週期中扮演了不一樣的角色,pre在子元素連接完成前執行,而post在子元素連接完成以後,這樣致使了父的post反而比子post晚一步執行

咱們簡單介紹了angular生命週期中兩個重要的過程,編譯階段與連接階段,經過這兩個階段,也解釋了爲何compile與pre執行像深度遍歷,而post像回溯的緣由。

最後,咱們將controller,link,compile工做職責作了一個簡單介紹,link與controller很像,但若是你想指令屬性方法複用,推薦綁定在controller上,若是隻是指令本身使用,推薦加在link上。

最後我還要留一個疑問,爲何在最後的例子中,指令echo1想複用指令echo controller上的方法,方法sayName是綁定在this上的,若是綁在scope上能不能複用呢?angular中的scope和this究竟是什麼關係?這個我會在下篇博客中好好介紹。

博客已更新  angularjs $scope與this的區別,controller as vm有何含義? 

那麼本文到這裏,結束。

相關文章
相關標籤/搜索