angular核心原理解析1:angular自啟動過程


angularJS的源代碼整體上來說是一個自執行函數,在angularJS加載完成后,就會自動執行了。

angular源代碼中:

angular = window.angular || (window.angular = {})

定義一個全局的angular空對象。

然后:

bindJQuery();    //綁定jQuery

publishExternalAPI(angular);   //擴展angular對象的方法和屬性

jqLite(document).ready(function() {
        angularInit(document, bootstrap);
});

var injector = angular.injector();     //得到一個注入器實例對象,它總共有5個方法:annotate,get,has,instantiate,invoke。annotate是用來分析函數的簽名的,在angular進行依賴注入的時候,你的函數的參數,你不需要new出來這個參數的對象,你只要告訴它你的函數需要什么東西,angular需要使用這個annotate來分析這些函數的參數。

angular啟動總共有三種方法:

第一種:默認方式,在頁面的元素節點上添加ng-app。angular會自動啟動。

第二種:不在頁面上添加任何的ng-app,我們手動啟動:

angular.element(document).ready(function(){

  angular.bootstrap(document, ["myModule"]);    //myModule是你創建的模塊的名字

});

第三種:在頁面上,添加兩個ng-app,但是必須在並行的兩個元素上添加。而且,angular只會自動啟動第一個ng-app,第二個ng-app需要你按照第二種方法,手動啟動。但是這種方式,會打印出異常。這種方式,基本上不會使用,可以忽略。

function bindJQuery() {
        // bind to jQuery if present;
        jQuery = window.jQuery;
        // reset to jQuery or default to us.
        if (jQuery) {
            jqLite = jQuery;
            extend(jQuery.fn, {
                scope: JQLitePrototype.scope,
                isolateScope: JQLitePrototype.isolateScope,
                controller: JQLitePrototype.controller,
                injector: JQLitePrototype.injector,
                inheritedData: JQLitePrototype.inheritedData
            });
            // Method signature:
            //     jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
            jqLitePatchJQueryRemove('remove', true, true, false);
            jqLitePatchJQueryRemove('empty', false, false, false);
            jqLitePatchJQueryRemove('html', false, false, true);
        } else {
            jqLite = JQLite;
        }
        angular.element = jqLite;
}

如果你引入了jQuery,就使用引入的jQuery。如果你沒有引入外部的jQuery,那么就使用angular自帶的JQLite。最后,設置angular.element = jqLite;

publishExternalAPI方法中,最重要的是setupModuleLoader方法:給angular構建模塊加載器。

function setupModuleLoader(window) {
     function ensure(obj, name, factory) {
            return obj[name] || (obj[name] = factory());
        }
        var angular = ensure(window, 'angular', Object);    //設置window.angular等於一個空對象
     return ensure(angular, 'module', function() {         //把angular.module設置成這個module函數,並返回這個函數。   
            var modules = {};   //當我們使用angular.module創建一個模塊時,都會緩存在變量modules中。
            return function module(name, requires, configFn) {   //當我們通過var demo1 = angular.module('demoApp', []);創建一個模塊時,它返回的是moduleInstance。而這個moduleInstance對象有factory(),controller(),directive(),config(),run()等方法可以調用。
          if (requires && modules.hasOwnProperty(name)) {   //如果有同名的模塊已經創建過,就把以前的那個模塊刪除。這里使用的是一個閉包,因為每次調用angular.module進行模塊的創建時,訪問的modules對象是在外層的匿名函數中定義的,本來一個函數執行結束后,就會銷毀里面的變量,雖然這里匿名函數已經執行結束了,但是由於內部函數module引用了此變量modules,所以即便外層的匿名函數執行結束了,它里面定義的modules變量也不會銷毀。通過閉包,我們可以定義一個私有的變量modules,只能通過規定的方法angular.module進行訪問,外部無法操作這個私有的變量modules。
                    modules[name] = null;
                } 
                return ensure(modules, name, function() {      //modules[demoApp] = moduleInstance,並返回這個moduleInstance。
                    var invokeQueue = [];
                    var runBlocks = [];
                    var config = invokeLater('$injector', 'invoke');
                    var moduleInstance = {    //模塊實例的方法
                        requires: requires,
                        provider: invokeLater('$provide', 'provider'),
                        factory: invokeLater('$provide', 'factory'),
                        service: invokeLater('$provide', 'service'),
                        value: invokeLater('$provide', 'value'),
                        constant: invokeLater('$provide', 'constant', 'unshift'),
                        animation: invokeLater('$animateProvider', 'register'),
                        filter: invokeLater('$filterProvider', 'register'),       //當我們通過一個模塊實例創建一個過濾器時,調用的是invokeLater方法返回的匿名函數function(){    invokeQueue['push']([$filterProvider, register, arguments]);   return moduleInstance;    }
                        controller: invokeLater('$controllerProvider', 'register'),                  
                        directive: invokeLater('$compileProvider', 'directive'),               
                        config: config,               
                        run: function(block) {
                            runBlocks.push(block);
                            return this;
                        }
                    };
            
            if(configFn){    //當調用angular.module方法傳入了三個參數時,就會執行config方法,上面在定義ng模塊時,就會傳入第三個參數。
              config(configFn);    //config方法其實就是invokeLater方法執行后的返回值。這里執行之后,也是對數組invokeQueue進行push操作。當ng模塊創建時,invokeQueue = [  [ $injector, invoke, [[$provide, function ngModule(){}]] ]    ]。
            }

                    return  moduleInstance;
                    function invokeLater(provider, method, insertMethod) {
                        return function() {
                            invokeQueue[insertMethod || 'push']([provider, method, arguments]);
                            return moduleInstance;
                        };
                    }
                });
            };
        });
    }

當我們定義了一個var myModule = angular.module("myModule", [] );模塊時,通過模塊myModule調用controller,directive,service方法分別創建控制器,指令,服務的這些方法都是在上面的moduleInstance對象中定義的。這里的myModule就是moduleInstance對象的一個實例。

publishExternalAPI方法構建好模塊加載器之后,會先把自己的內核模塊加載起來,比如:把ng模塊,ngLocale模塊注冊進來,同時,會把一些內核的指令注冊進來。

function publishExternalAPI(angular){
        extend(angular, {              //綁定方法到angular對象上
              'bootstrap': bootstrap,

        'extend': extend,

        'element': jqLite,

        'injector': createInjector,
         ......
        });
        angularModule = setupModuleLoader(window);      // 此方法會把angular對象綁定到window上,然后把一個函數綁定到angular的module屬性上,最后返回這個函數,這個函數是一個模塊加載器,主要作用是創建和獲取模塊。這里的angularModule函數就是angular.module函數。
        try {
            angularModule('ngLocale');
        } catch (e) {
            angularModule('ngLocale', []).provider('$locale', $LocaleProvider);    //創建一個名為ngLocale的模塊,並在這個模塊上定義一個名為$locale的$LocaleProvider服務提供者。這里的provider方法,是把方法中的參數都存到invokeQueue數組中,以便在后面調用,從setupModuleLoader方法中很容易知道。
        }
        angularModule('ng', ['ngLocale'], ['$provide',      //創建一個名為ng的模塊,這個模塊依賴於ngLocale模塊。
            function ngModule($provide) {        
                $provide.provider({
                    $$sanitizeUri: $$SanitizeUriProvider
                });
                $provide.provider('$compile', $CompileProvider).     //ng模塊中,定義一個名為$compile的$CompileProvider服務提供者
                    directive({  
                        a: htmlAnchorDirective,
                        input: inputDirective,
                        textarea: inputDirective,
                        form: formDirective,
                        option: optionDirective,
                        ngBind: ngBindDirective,                 
                        ngClass: ngClassDirective,
                        ngController: ngControllerDirective,
                        ngForm: ngFormDirective,
                        ngHide: ngHideDirective,
                        ngIf: ngIfDirective,
                        ngInit: ngInitDirective,
                        ngRepeat: ngRepeatDirective,
                        ngShow: ngShowDirective,
                        ngOptions: ngOptionsDirective,
                        ngTransclude: ngTranscludeDirective,
                        ngModel: ngModelDirective,
                        ngList: ngListDirective,
                        ngChange: ngChangeDirective,
                        required: requiredDirective,
                        ngRequired: requiredDirective,
                        ngValue: ngValueDirective
                    });
                $provide.provider({          //在ng模塊中,定義一系列的服務提供者
                    $animate: $AnimateProvider,                
                    $controller: $ControllerProvider,
                    $filter: $FilterProvider,
                    $http: $HttpProvider,
                    $location: $LocationProvider,
                    $parse: $ParseProvider,
                    $rootScope: $RootScopeProvider,
                    $window: $WindowProvider
                });
            }
        ]);
    }

最后,就是angularInit方法:此方法會去判斷頁面上是否有ng-app,如果有,就調用bootstrap方法進行啟動。如果沒有,你就需要自己手動去啟動了。

function angularInit(element, bootstrap) {   
        var elements = [element],  
            appElement,
            module,
            names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],   //angular有4種方式在頁面上定義angular應用
            NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
        function append(element) {    
            element && elements.push(element);
        }
        forEach(names, function(name) {
            names[name] = true;
            append(document.getElementById(name));
            name = name.replace(':', '\\:');
            if (element.querySelectorAll) {
                forEach(element.querySelectorAll('.' + name), append);
                forEach(element.querySelectorAll('.' + name + '\\:'), append);
                forEach(element.querySelectorAll('[' + name + ']'), append);
            }
        });    //針對4種定義方式,在頁面上獲取定義為angular應用的元素節點,
        forEach(elements, function(element) {
            if (!appElement) {    //appElement只定義一次
                var className = ' ' + element.className + ' ';
                var match = NG_APP_CLASS_REGEXP.exec(className);    //使用正則表達式去匹配
                if (match) {
                    appElement = element;
                    module = (match[2] || '').replace(/\s+/g, ',');
                } else {
                    forEach(element.attributes, function(attr) {
                        if (!appElement && names[attr.name]) {
                            appElement = element;
                            module = attr.value;
                        }
                    });
                }
            }
        });    //定義angular應用的元素,也就是在頁面上寫有ng-app(有4種方式定義)的元素定義為appElement。
        if (appElement) {     
            bootstrap(appElement, module ? [module] : []); //如果頁面上有定義angular應用的元素,就啟動。
        }
    }

調用bootstrap方法,才代表angular真正開始啟動:

function bootstrap(element, modules) {
        var doBootstrap = function() {      //定義一個函數
            element = jqLite(element);
            if (element.injector()) {    //如果此元素的angular應用已經啟動過了,就拋出錯誤
                var tag = (element[0] === document) ? 'document' : startingTag(element);
                throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
            }
            modules = modules || [];
            modules.unshift(['$provide', function($provide) {
                $provide.value('$rootElement', element);
            }]);
            modules.unshift('ng');     //這里,我們假設在頁面上定義了ng-app的元素,沒有添加任何的多余的東西。因此這里的modules=["ng",["$provide",function($provide){}]]。
            var injector = createInjector(modules);   //這個方法非常重要,它把我們angular應用需要初始化的模塊數組傳進去后,進行加載,並創建一個注冊器實例對象,最后返回這個注冊器實例對象。
            injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',  //調用注冊器實例對象的invoke方法
                function(scope, element, compile, injector, animate) {
                    scope.$apply(function() {
                        element.data('$injector', injector);
                        compile(element)(scope);    
                    });
                }]
            );
            return injector;     
        };
        var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
        if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {   //如果window.name不是以NG_DEFER_BOOTSTRAP!開頭的話,就進入if語句,執行上面定義的方法。
            return doBootstrap();     
        }
        window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
        angular.resumeBootstrap = function(extraModules) {
            forEach(extraModules, function(module) {
                modules.push(module);
            });
            doBootstrap();
        };
    }

上面的代碼會創建一個注入器,也就是說angular在啟動時,會創建一個注入器。一個angular應用只有一個注入器。當創建好注入器后,它會首先把內核加載起來,通過compile方法。

 

 

 

 

加油!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM