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方法。

加油!

时间: 2024-10-29 00:11:08

angular核心原理解析1:angular自启动过程的相关文章

angular核心原理解析3:指令的执行过程

指令的执行过程分析. 我们知道指令的执行分两个阶段,一个是compile,一个是link. 我们可以在指令中自定义compile和link. 首先,我们来讲解如何自定义link函数 举个例子: <!doctype html> <html ng-app="myModule"> <head> </head> <body> <hello></hello> </body> <script sr

angular核心原理解析2:注入器的创建和使用

上一课没有讲到创建注入器的方法createInjector. 此方法,会创建两种不同的注入器:第一种叫做providerInjector,第二种叫做instanceInjector.providerInjector是用来创建provider的,instanceInjector是用来创建一个对象实例的. 我们可以在js代码中直接使用注入器: var myModule = angular.module("myModule", []); myModule.factory("pers

Promise核心原理解析

作者: HerryLo 本文永久有效链接: https://github.com/AttemptWeb...... Promises对象被用于表示一个异步操作的最终完成 (或失败), 及其结果值.主要是为了解决异步操作的问题. #Promise对象的状态 一个 Promise对象有以下三种状态: pending: 初始状态,既不是成功,也不是失败状态. fulfilled(resolved): 意味着操作成功完成. rejected: 意味着操作失败. Promise对象内部运行的一个变化, 变

GeoHash核心原理解析 - OPEN 开发经验库

阅读目录 引子 一.感性认识GeoHash 二.GeoHash算法的步骤 三.GeoHash Base32编码长度与精度 三.GeoHash算法 四.使用注意点 引子 机机是个好动又好学的孩子,平日里就喜欢拿着手机地图点点按按来查询一些好玩的东西.某一天机机到北海公园游玩,肚肚饿了,于是乎打开手机地图,搜索北海公园附近的餐馆,并选了其中一家用餐. 饭饱之后机机开始反思了,地图后台如何根据自己所在位置查询来查询附近餐馆的呢?苦思冥想了半天,机机想出了个方法:计算所在位置P与北京所有餐馆 的距离,然

GeoHash核心原理解析

引子 机机是个好动又好学的孩子,平日里就喜欢拿着手机地图点点按按来查询一些好玩的东西.某一天机机到北海公园游玩,肚肚饿了,于是乎打开手机地图,搜索北海公园附近的餐馆,并选了其中一家用餐. 饭饱之后机机开始反思了,地图后台如何根据自己所在位置查询来查询附近餐馆的呢?苦思冥想了半天,机机想出了个方法:计算所在位置P与北京所有餐馆的距离,然后返回距离<=1000米的餐馆.小得意了一会儿,机机发现北京的餐馆何其多啊,这样计算不得了,于是想了,既然知道经纬度了,那它应该知道自己在西城区,那应该计算所在位置

GeoHash原理解析

GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段进行排序,然后通过类似二分查找的方法进行快速查找,即它要求索引的字段是可排序的,一般而言,可排序的是一维字段,比如时间.年龄.薪水等等.但是对于空间上的一个点(二维,包括经度和纬度),如何排序呢?又如何索引呢?解决的方法很多,下文介绍一种方法来解决这一问题.   思想:如果能通过某种方法将二维的点数

AngularJS源码解析1:angular自启动过程

angularJS加载进来后,会有一个立即执行函数调用,在源代码的最下面是angular初始化的地方.代码展示: bindJQuery(); publishExternalAPI(angular); jqLite(document).ready(function() { angularInit(document, bootstrap); }); bindJQuery方法的作用是:检查你的主页面是否引用了jquery,没有的话,就用angular本身自带的JQLite,否则使用你引用的jquery

Web API之过滤器执行过程原理解析【二】(十一)

前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授权上的自定义特性的执行过程. APiController 之前有讲到该APiController,也就稍微介绍了,这节我们来详细此Web API控制器的基类: 1 public abstract class ApiController : IHttpController, IDisposable 2

python easy_install centos 下安装过程和原理解析

一.easy_install 安装过程 其安装过程有很多种,我也找了很多的例子,但是结果都不太好,以下方法的结果是不错的. easy_install与yum类似,使用easy_install,可以轻松在pypi软件库里面搜索python各类软件 安装easy_install比较简单,如果配置好yum,就可以直接搜索python-setuptools yum –y install python-setuptools 安装完python-setuptools之后,还需要安装python-devel