angular源码理解

注:代码太长,建议将代码下载至本地,然后用自己喜欢的编辑器查看。

loader.js [下载]

'use strict';

/**
 * 在angular中,filter、directive、controller、service、factory等都是带有$get属性的provider
 */

/**
 * @ngdoc type
 * @name angular.Module
 * @module ng
 * @description
 *
 * 用于配置angular模块的接口
 * Interface for configuring angular {@link angular.module modules}.
 */

function setupModuleLoader(window) {

    var $injectorMinErr = minErr('$injector');
    var ngMinErr = minErr('ng');

    /**
     * 确保obj[name]存在,并且指向factory()
     */
    function ensure(obj, name, factory) {
        return obj[name] || (obj[name] = factory());
    }

    // 实例化一个angular对象
    var angular = ensure(window, 'angular', Object);

    // 将$$minErr支出去,有些模块在引导中可能会用到
    // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
    angular.$$minErr = angular.$$minErr || minErr;

    return ensure(angular, 'module', function() {
        /** @type {Object.<string, angular.Module>} */
        var modules = {};

        /**
         * @ngdoc function
         * @name angular.module
         * @module ng
         * @description
         *
         * The `angular.module` is a global place for creating, registering and retrieving Angular
         * modules.
         * All modules (angular core or 3rd party) that should be available to an application must be
         * registered using this mechanism.
         *
         * When passed two or more arguments, a new module is created.  If passed only one argument, an
         * existing module (the name passed as the first argument to `module`) is retrieved.
         *
         *
         * # Module
         *
         * A module is a collection of services, directives, controllers, filters, and configuration information.
         * `angular.module` is used to configure the {@link auto.$injector $injector}.
         *
         * ```js
         * // Create a new module
         * var myModule = angular.module('myModule', []);
         *
         * // register a new service
         * myModule.value('appName', 'MyCoolApp');
         *
         * // configure existing services inside initialization blocks.
         * myModule.config(['$locationProvider', function($locationProvider) {
         *   // Configure existing providers
         *   $locationProvider.hashPrefix('!');
         * }]);
         * ```
         *
         * Then you can create an injector and load your modules like this:
         *
         * ```js
         * var injector = angular.injector(['ng', 'myModule'])
         * ```
         *
         * However it's more likely that you'll just use
         * {@link ng.directive:ngApp ngApp} or
         * {@link angular.bootstrap} to simplify this process for you.
         *
         * @param {!string} name The name of the module to create or retrieve.
         * @param {!Array.<string>=} requires If specified then new module is being created. If
         *        unspecified then the module is being retrieved for further configuration.
         * @param {Function=} configFn Optional configuration function for the module. Same as
         *        {@link angular.Module#config Module#config()}.
         * @returns {module} new module with the {@link angular.Module} api.
         */
        return function module(name, requires, configFn) {
            var assertNotHasOwnProperty = function(name, context) {
                if (name === 'hasOwnProperty') {
                    throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
                }
            };

            // 不允许name是hasOwnProperty
            assertNotHasOwnProperty(name, 'module');
            // 在依赖的模块不为空的前提下,如果modules里面已经存在了name所示的模块,则将其置为null,
            // 这样后面的ensure函数才会重新创建模块
            if (requires && modules.hasOwnProperty(name)) {
                modules[name] = null;
            }
            return ensure(modules, name, function() {
                // 如果依赖模块不存在(也不为空数组),那就说明不是创建模块,
                // 此时还进入这个函数,就直接抛出异常,因为这个函数是用于创建模块的
                if (!requires) {
                    throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
                        "the module name or forgot to load it. If registering a module ensure that you " +
                        "specify the dependencies as the second argument.", name);
                }

                // [
                //  ['$provide', 'constant', (一堆后面传进来的参数)],
                //  ['$provide', 'provider', (一堆后面传进来的参数)],
                //  ['$provide', 'factory', (一堆后面传进来的参数)],
                //  ['$provide', 'service', (一堆后面传进来的参数)],
                //  ['$provide', 'value', (一堆后面传进来的参数)],
                //  ['$animationProvider', 'register', (一堆后面传进来的参数)],
                //  ['$filterProvider', 'register', (一堆后面传进来的参数)],
                //  ['$controllerProvider', 'register', (一堆后面传进来的参数)],
                //  ['$compileProvider', 'directive', (一堆后面传进来的参数)]
                // ]
                // 其中“一堆后面传进来的参数”是外部传进来的,比如:
                // angular.module(...).factory('testFactory', ['$rootScope', function() {}])
                // 那么此时这些参数就是一个类数组:['testFactory', ['$rootScope', function() {}]]
                /** @type {!Array.<Array.<*>>} */
                var invokeQueue = [];

                // [
                //  ['$injector', 'invoke', (一堆后面传进来的参数)]
                // ]
                /** @type {!Array.<Function>} */
                var configBlocks = [];

                // 外部用module.run()注册进来的一堆函数
                /** @type {!Array.<Function>} */
                var runBlocks = [];

                var config = invokeLater('$injector', 'invoke', 'push', configBlocks);

                /** @type {angular.Module} */
                var moduleInstance = {
                    // Private state
                    _invokeQueue: invokeQueue,
                    _configBlocks: configBlocks,
                    _runBlocks: runBlocks,

                    /**
                     * @ngdoc property
                     * @name angular.Module#requires
                     * @module ng
                     *
                     * @description
                     * Holds the list of modules which the injector will load before the current module is
                     * loaded.
                     */
                    requires: requires,

                    /**
                     * @ngdoc property
                     * @name angular.Module#name
                     * @module ng
                     *
                     * @description
                     * Name of the module.
                     */
                    name: name,


                    /**
                     * @ngdoc method
                     * @name angular.Module#provider
                     * @module ng
                     * @param {string} name service name
                     * @param {Function} providerType Construction function for creating new instance of the
                     *                                service.
                     * @description
                     * See {@link auto.$provide#provider $provide.provider()}.
                     */
                    provider: invokeLater('$provide', 'provider'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#factory
                     * @module ng
                     * @param {string} name service name
                     * @param {Function} providerFunction Function for creating new instance of the service.
                     * @description
                     * See {@link auto.$provide#factory $provide.factory()}.
                     */
                    factory: invokeLater('$provide', 'factory'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#service
                     * @module ng
                     * @param {string} name service name
                     * @param {Function} constructor A constructor function that will be instantiated.
                     * @description
                     * See {@link auto.$provide#service $provide.service()}.
                     */
                    service: invokeLater('$provide', 'service'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#value
                     * @module ng
                     * @param {string} name service name
                     * @param {*} object Service instance object.
                     * @description
                     * See {@link auto.$provide#value $provide.value()}.
                     */
                    value: invokeLater('$provide', 'value'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#constant
                     * @module ng
                     * @param {string} name constant name
                     * @param {*} object Constant value.
                     * @description
                     * Because the constant are fixed, they get applied before other provide methods.
                     * See {@link auto.$provide#constant $provide.constant()}.
                     */
                    constant: invokeLater('$provide', 'constant', 'unshift'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#animation
                     * @module ng
                     * @param {string} name animation name
                     * @param {Function} animationFactory Factory function for creating new instance of an
                     *                                    animation.
                     * @description
                     *
                     * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
                     *
                     *
                     * Defines an animation hook that can be later used with
                     * {@link ngAnimate.$animate $animate} service and directives that use this service.
                     *
                     * ```js
                     * module.animation('.animation-name', function($inject1, $inject2) {
                     *   return {
                     *     eventName : function(element, done) {
                     *       //code to run the animation
                     *       //once complete, then run done()
                     *       return function cancellationFunction(element) {
                     *         //code to cancel the animation
                     *       }
                     *     }
                     *   }
                     * })
                     * ```
                     *
                     * See {@link ng.$animateProvider#register $animateProvider.register()} and
                     * {@link ngAnimate ngAnimate module} for more information.
                     */
                    animation: invokeLater('$animateProvider', 'register'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#filter
                     * @module ng
                     * @param {string} name Filter name.
                     * @param {Function} filterFactory Factory function for creating new instance of filter.
                     * @description
                     * See {@link ng.$filterProvider#register $filterProvider.register()}.
                     */
                    filter: invokeLater('$filterProvider', 'register'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#controller
                     * @module ng
                     * @param {string|Object} name Controller name, or an object map of controllers where the
                     *    keys are the names and the values are the constructors.
                     * @param {Function} constructor Controller constructor function.
                     * @description
                     * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
                     */
                    controller: invokeLater('$controllerProvider', 'register'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#directive
                     * @module ng
                     * @param {string|Object} name Directive name, or an object map of directives where the
                     *    keys are the names and the values are the factories.
                     * @param {Function} directiveFactory Factory function for creating new instance of
                     * directives.
                     * @description
                     * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
                     */
                    directive: invokeLater('$compileProvider', 'directive'),

                    /**
                     * @ngdoc method
                     * @name angular.Module#config
                     * @module ng
                     * @param {Function} configFn Execute this function on module load. Useful for service
                     *    configuration.
                     * @description
                     * Use this method to register work which needs to be performed on module loading.
                     * For more about how to configure services, see
                     * {@link providers#provider-recipe Provider Recipe}.
                     */
                    config: config,

                    /**
                     * @ngdoc method
                     * @name angular.Module#run
                     * @module ng
                     * @param {Function} initializationFn Execute this function after injector creation.
                     *    Useful for application initialization.
                     * @description
                     * Use this method to register work which should be performed when the injector is done
                     * loading all modules.
                     */
                    run: function(block) {
                        runBlocks.push(block);
                        return this;
                    }
                };

                // 如果configFn存在,就放在configBlocks中,此时configBlocks为:
                // [
                //  ['$injector', 'invoke', [configFn]]
                // ]
                if (configFn) {
                    config(configFn);
                }

                return moduleInstance;

                /**
                 * @param {string} provider
                 * @param {string} method
                 * @param {String=} insertMethod
                 * @returns {angular.Module}
                 */
                function invokeLater(provider, method, insertMethod, queue) {
                    // queue的数据结构:
                    // [
                    //  ['$filterProvider', 'register', (一堆后面传进来的参数)],
                    //  ['$compileProvider', 'directive', (一堆后面传进来的参数)],
                    // ]
                    if (!queue) queue = invokeQueue;
                    return function() {
                        queue[insertMethod || 'push']([provider, method, arguments]);
                        return moduleInstance;
                    };
                }
            });
        };
    });

}

auto/injector.js [下载]

'use strict';

/**
 * @ngdoc function
 * @module ng
 * @name angular.injector
 * @kind function
 *
 * @description
 * 创建一个注入器,用于获取services和依赖注入
 *
 * @param {Array.<string|Function>} modules 一组模块函数或者他们的别名。必须显示地添加ng模块。
 * @param {boolean=} [strictDi=false] 注入器是否应该是严格的模式,严格模式禁止用函数参数的方式来实现依赖注入(function($scope) {})。
 * @returns {injector} 返回注入器对象。
 *
 * @example
 * Typical usage
 * ```js
 *   // 创建一个注入器
 *   var $injector = angular.injector(['ng']);
 *
 *   // 使用注入器来驱动你的应用
 *   // 使用类型引用来自动地注入参数(['$scope', function($scope) {}]),或者使用隐式注入(function($scope) {})
 *   $injector.invoke(function($rootScope, $compile, $document) {
 *     $compile($document)($rootScope);
 *     $rootScope.$digest();
 *   });
 * ```
 *
 * 有时你想从外界的angular获取当前运行的angular应用的注入器。或许你想在应用引导完成之后注入和编译一些标记(标签)。
 * 你可以用JQuery/jqLite的额外方法injector()来实现这个功能。
 *
 * 很少会这样干,但是一些第三方的库可能会注入标记(标签)
 *
 * 在下面的这个例子中,一段包含ng-controller指令的HTML代码被jQuery添加到文档的末尾。
 * 接下来我们将它编译连接到当前的AngularJS scope。
 *
 * ```js
 * var $div = $('<div ng-controller="MyCtrl"></div>');
 * $(document.body).append($div);
 *
 * angular.element(document).injector().invoke(function($compile) {
 *   var scope = angular.element($div).scope();
 *   $compile($div)(scope);
 * });
 * ```
 */


/**
 * @ngdoc module
 * @name auto
 * @description
 *
 * 一个隐式的模块,会被自动添加到每一个注入器。
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;      // 匹配函数中的参数
var FN_ARG_SPLIT = /,/;                                  // 用于分离用逗号隔开的一对参数
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; // 匹配代码注释
var $injectorMinErr = minErr('$injector');

/**
 * 将函数fn转换成比较干净的字符串形式,
 * 比如anonFn(function test(a, b, c) {}),会被转换成字符串'function(a, b, c)'
 */
function anonFn(fn) {
    // 用于匿名函数,显示出来的函数特征至少可以帮助debug
    var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
        args = fnText.match(FN_ARGS);
    if (args) {
        return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
    }
    return 'fn';
}

/**
 * 主要用于解析出依赖,
 * 比如:
 *     function($scope, $compile) {},解析出的依赖是['$scope', '$compile']
 *     ['$scope', '$compile', function($scope, $compile) {}]解析出的依赖也是['$scope', '$compile']
 */
function annotate(fn, strictDi, name) {
    var $inject,
        fnText,
        argDecl,
        last;

    // fn是一个函数
    if (typeof fn === 'function') {
        // 如果fn中没有明确指定$inject,则开始修复
        if (!($inject = fn.$inject)) {
            $inject = [];
            if (fn.length) {
                // 如果是严格的模式,则抛出异常,
                // 因为严格模式不支持function($compile, $scope) {}这种形式的依赖注入
                if (strictDi) {
                    if (!isString(name) || !name) {
                        name = fn.name || anonFn(fn);
                    }
                    throw $injectorMinErr('strictdi',
                        '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
                }
                // 将fn函数转换成字符串,并去掉其中的注释
                fnText = fn.toString().replace(STRIP_COMMENTS, '');
                // 提取函数中的参数,是一个用逗号隔开的字符串
                argDecl = fnText.match(FN_ARGS);
                // 遍历每一个参数名字,将名字提取到$inject数组中
                forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
                    arg.replace(FN_ARG, function(all, underscore, name) {
                        $inject.push(name);
                    });
                });
            }
            // 将$inject数组设置到fn函数上面去
            fn.$inject = $inject;
        }
    }
    // fn是一个数组,类似于['$scope', function($scope) {}]
    else if (isArray(fn)) {
        last = fn.length - 1;
        // fn数组最后一个元素必须是一个函数
        assertArgFn(fn[last], 'fn');
        // 出去最后一个元素,前面的都是依赖
        $inject = fn.slice(0, last);
    } else {
        assertArgFn(fn, 'fn', true);
    }
    return $inject;
}

///////////////////////////////////////

/**
 * @ngdoc service
 * @name $injector
 *
 * @description
 *
 * $injector用于获取对象实例,这些对象实例用provider、instantiate types、
 * invoke methods和load modules的方式定义出来。
 *
 * 下面的例子总是true:
 *
 * ```js
 *   var $injector = angular.injector();
 *   expect($injector.get('$injector')).toBe($injector);
 *   expect($injector.invoke(function($injector) {
 *     return $injector;
 *   })).toBe($injector);
 * ```
 *
 * # Injection Function Annotation
 *
 * JavaScript does not have annotations, and annotations are needed for dependency injection. The
 * following are all valid ways of annotating function with injection arguments and are equivalent.
 *
 * ```js
 *   // 隐式注入(仅在代码没有被压缩或者混淆的前提下有效)
 *   $injector.invoke(function(serviceA){});
 *
 *   // annotated
 *   function explicit(serviceA) {};
 *   explicit.$inject = ['serviceA'];
 *   $injector.invoke(explicit);
 *
 *   // inline
 *   $injector.invoke(['serviceA', function(serviceA){}]);
 * ```
 *
 * ## 结论
 *
 * 在JavaScript调用一个函数的toString()方法会返回这个函数的定义。
 * 可以从返回的定义(字符串)中解析出函数的参数。这种找到注解的方式
 * 在严格模式下面是禁止的。
 * 注意:压缩和使用混淆的时候,这种方式就不会奏效了,因为会改变参数的名字。
 *
 * ## `$inject` Annotation
 * 通过设置$inject属性到函数上面,注入的参数可以用这个属性来指定。
 *
 * ## Inline
 * 注入参数的名称数组的形式,最后一个元素是将被调用的函数。
 */

/**
 * @ngdoc method
 * @name $injector#get
 *
 * @description
 * 返回service的实例
 *
 * @param {string} name 要获取的实例名称。
 * @param {string} caller An optional string to provide the origin of the function call for error messages.
 * @return {*} 返回最后的实例。
 */

/**
 * @ngdoc method
 * @name $injector#invoke
 *
 * @description
 * 使用$injector中的参数来调用方法。
 *
 * @param {!Function} fn 被调用的方法。 函数的参数根据注解规则来注入。
 * @param {Object=} self 被调用函数中的this变量。
 * @param {Object=} locals 可选。如果设置了,那么要注入的参数首先根据参数名到这里面取值,而不是去$injector里面获取。
 * @returns {*} 返回被调用函数的返回值。
 */

/**
 * @ngdoc method
 * @name $injector#has
 *
 * @description
 * 允许用户查询指定的service是否存在。
 *
 * @param {string} name 用于查询的service名字。
 * @returns {boolean} 如果存在,返回true。
 */

/**
 * @ngdoc method
 * @name $injector#instantiate
 * @description
 * Create a new instance of JS type. The method takes a constructor function, invokes the new
 * operator, and supplies all of the arguments to the constructor function as specified by the
 * constructor annotation.
 *
 * @param {Function} Type Annotated constructor function.
 * @param {Object=} locals Optional object. If preset then any argument names are read from this
 * object first, before the `$injector` is consulted.
 * @returns {Object} new instance of `Type`.
 */

/**
 * @ngdoc method
 * @name $injector#annotate
 *
 * @description
 * 返回一组service名字,。。。(Returns an array of service names which the function is requesting for injection.)
 * 当函数被调用的时候,注入器使用这个API来决定注入哪些service到这个函数。
 * 有三种方式可以实现这个函数的依赖注解。
 *
 * # 函数参数名字
 *
 * 最简单的形式是从函数的参数中取出依赖。这种方式是通过将函数用toString()方法转换成字符串,然后取出参数名字来实现的。
 * ```js
 *   // Given
 *   function MyController($scope, $route) {
 *     // ...
 *   }
 *
 *   // Then
 *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
 * ```
 *
 * 你可以通过使用严格的注入模式来禁止这种方式。
 *
 * 这种方式在代码压缩、混淆的时候无效。正视由于这个原因,下面的注解策略也被支持。
 *
 * # $inject属性
 *
 * 如果函数有$inject属性,并且它的值是一个字符串数组,这串字符代表要被注入到函数的service的名字。
 * ```js
 *   // Given
 *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
 *     // ...
 *   }
 *   // Define function dependencies
 *   MyController['$inject'] = ['$scope', '$route'];
 *
 *   // Then
 *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
 * ```
 *
 * # 数组标记
 *
 * It is often desirable to inline Injected functions and that's when setting the `$inject` property
 * is very inconvenient. In these situations using the array notation to specify the dependencies in
 * a way that survives minification is a better choice:
 *
 * ```js
 *   // We wish to write this (not minification / obfuscation safe)
 *   injector.invoke(function($compile, $rootScope) {
 *     // ...
 *   });
 *
 *   // We are forced to write break inlining
 *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
 *     // ...
 *   };
 *   tmpFn.$inject = ['$compile', '$rootScope'];
 *   injector.invoke(tmpFn);
 *
 *   // To better support inline function the inline annotation is supported
 *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
 *     // ...
 *   }]);
 *
 *   // Therefore
 *   expect(injector.annotate(
 *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
 *    ).toEqual(['$compile', '$rootScope']);
 * ```
 *
 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
 * be retrieved as described above.
 *
 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
 *
 * @returns {Array.<string>} The names of the services which the function requires.
 */



/**
 * @ngdoc service
 * @name $provide
 *
 * @description
 *
 * $provide service有一系列的方法运用$injector来注册组件。其中许多方法也暴露在angular.Module上面。
 *
 * 一个Angular的service是一个service factory创建的单例对象。
 * 这些service factory是一个service provider依次创建的。
 * 这些service providers是构造函数,在被实例化的时候必须包含一个叫做$get的属性,这个$get属性保存了service factory函数。
 *
 * 当你请求一个service,$injector有负责找到正确的service provider,实例化这个service provider,然后
 * 调用$get service factory函数获取这个service的实例。
 *
 * 一般service没有配置选项,也没有必要给service factory添加方法。
 * provider仅仅是一个有$get属性的构造函数,由此,$provide service有附加的辅助方法来注册service而不指定provider。
 *
 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
 *     {@link auto.$injector $injector}
 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
 *     providers and services.
 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
 *     services, not providers.
 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
 *     that will be wrapped in a **service provider** object, whose `$get` property will contain the
 *     given factory function.
 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
 *     that will be wrapped in a **service provider** object, whose `$get` property will instantiate
 *      a new object using the given constructor function.
 *
 * See the individual methods for more information and examples.
 */

/**
 * @ngdoc method
 * @name $provide#provider
 * @description
 *
 * 利用$injector注册一个provider function。provider function是构造函数,这个构造函数的实例负责构建某一个service。
 *
 * service provider的名字以它将要构造的service名字加上一个后缀Provider命名(例如$log对应$logProvider)。
 *
 * service provider对象可以包含额外的方法,用于配置这个provider和它的service。重要的是,你能够配置通过$get方法创建何种的service,
 * 或者service将会如何实现功能。例如,$logProvider有一个方法debugEnabled(),这个方法让你指定service是否向控制台输出debug信息。
 *
 * @param {string} name 实例的名字。注意:provider的名字将会是[name + 'Provider']的形式。
 * @param {(Object|function())} provider 如果provider是:
 *
 *   - 对象: 这个对象必须具备一个$get方法。当需要创建一个实例的时候,这个$get方法就会被$injector.invoke()调用。
 *   - 构造函数: 将会通过$injector.instantiate()创建一个实例,接下来就被当成一个对象来处理了。
 *
 * @returns {Object} 注册好的provider实例

 * @例子
 *
 * 下面的例子展示了如何创建一个简单的事件追踪service,然后使用$provider.provider()注册。
 *
 * ```js
 *  // Define the eventTracker provider
 *  function EventTrackerProvider() {
 *    var trackingUrl = '/track';
 *
 *    // A provider method for configuring where the tracked events should been saved
 *    this.setTrackingUrl = function(url) {
 *      trackingUrl = url;
 *    };
 *
 *    // The service factory function
 *    this.$get = ['$http', function($http) {
 *      var trackedEvents = {};
 *      return {
 *        // Call this to track an event
 *        event: function(event) {
 *          var count = trackedEvents[event] || 0;
 *          count += 1;
 *          trackedEvents[event] = count;
 *          return count;
 *        },
 *        // Call this to save the tracked events to the trackingUrl
 *        save: function() {
 *          $http.post(trackingUrl, trackedEvents);
 *        }
 *      };
 *    }];
 *  }
 *
 *  describe('eventTracker', function() {
 *    var postSpy;
 *
 *    beforeEach(module(function($provide) {
 *      // Register the eventTracker provider
 *      $provide.provider('eventTracker', EventTrackerProvider);
 *    }));
 *
 *    beforeEach(module(function(eventTrackerProvider) {
 *      // Configure eventTracker provider
 *      eventTrackerProvider.setTrackingUrl('/custom-track');
 *    }));
 *
 *    it('tracks events', inject(function(eventTracker) {
 *      expect(eventTracker.event('login')).toEqual(1);
 *      expect(eventTracker.event('login')).toEqual(2);
 *    }));
 *
 *    it('saves to the tracking url', inject(function(eventTracker, $http) {
 *      postSpy = spyOn($http, 'post');
 *      eventTracker.event('login');
 *      eventTracker.save();
 *      expect(postSpy).toHaveBeenCalled();
 *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
 *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
 *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
 *    }));
 *  });
 * ```
 */

/**
 * @ngdoc method
 * @name $provide#factory
 * @description
 *
 * 注册一个service factory,这个service factory将会被调用返回响应的service实例。
 * This is short for registering a service where its provider consists of only a `$get` property,
 * which is the given service factory function.
 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
 * configure your service in a provider.
 *
 * @param {string} name The name of the instance.
 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
 *                            for `$provide.provider(name, {$get: $getFn})`.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here is an example of registering a service
 * ```js
 *   $provide.factory('ping', ['$http', function($http) {
 *     return function ping() {
 *       return $http.send('/ping');
 *     };
 *   }]);
 * ```
 * You would then inject and use this service like this:
 * ```js
 *   someModule.controller('Ctrl', ['ping', function(ping) {
 *     ping();
 *   }]);
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#service
 * @description
 *
 * Register a **service constructor**, which will be invoked with `new` to create the service
 * instance.
 * This is short for registering a service where its provider's `$get` property is the service
 * constructor function that will be used to instantiate the service instance.
 *
 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
 * as a type/class.
 *
 * @param {string} name The name of the instance.
 * @param {Function} constructor A class (constructor function) that will be instantiated.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here is an example of registering a service using
 * {@link auto.$provide#service $provide.service(class)}.
 * ```js
 *   var Ping = function($http) {
 *     this.$http = $http;
 *   };
 *
 *   Ping.$inject = ['$http'];
 *
 *   Ping.prototype.send = function() {
 *     return this.$http.get('/ping');
 *   };
 *   $provide.service('ping', Ping);
 * ```
 * You would then inject and use this service like this:
 * ```js
 *   someModule.controller('Ctrl', ['ping', function(ping) {
 *     ping.send();
 *   }]);
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#value
 * @description
 *
 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
 * number, an array, an object or a function.  This is short for registering a service where its
 * provider's `$get` property is a factory function that takes no arguments and returns the **value
 * service**.
 *
 * Value services are similar to constant services, except that they cannot be injected into a
 * module configuration function (see {@link angular.Module#config}) but they can be overridden by
 * an Angular
 * {@link auto.$provide#decorator decorator}.
 *
 * @param {string} name The name of the instance.
 * @param {*} value The value.
 * @returns {Object} registered provider instance
 *
 * @example
 * Here are some examples of creating value services.
 * ```js
 *   $provide.value('ADMIN_USER', 'admin');
 *
 *   $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
 *
 *   $provide.value('halfOf', function(value) {
 *     return value / 2;
 *   });
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#constant
 * @description
 *
 * Register a **constant service**, such as a string, a number, an array, an object or a function,
 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
 * be overridden by an Angular {@link auto.$provide#decorator decorator}.
 *
 * @param {string} name The name of the constant.
 * @param {*} value The constant value.
 * @returns {Object} registered instance
 *
 * @example
 * Here a some examples of creating constants:
 * ```js
 *   $provide.constant('SHARD_HEIGHT', 306);
 *
 *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
 *
 *   $provide.constant('double', function(value) {
 *     return value * 2;
 *   });
 * ```
 */


/**
 * @ngdoc method
 * @name $provide#decorator
 * @description
 *
 * 运用$injector注册一个service decorator。
 * 一个service decorator监听一个service的创建,允许覆盖或者修改service的功能。这个decorator返回的对象可能是原始的service,
 * 或者一个新的service对象,这个对象代替或者包装和代理原始的service。
 *
 * @param {string} name 要修饰的service。
 * @param {function()} decorator This function will be invoked when the service needs to be
 *    instantiated and should return the decorated service instance. The function is called using
 *    the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
 *    Local injection arguments:
 *
 *    * `$delegate` - The original service instance, which can be monkey patched, configured,
 *      decorated or delegated to.
 *
 * @example
 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
 * calls to {@link ng.$log#error $log.warn()}.
 * ```js
 *   $provide.decorator('$log', ['$delegate', function($delegate) {
 *     $delegate.warn = $delegate.error;
 *     return $delegate;
 *   }]);
 * ```
 */


function createInjector(modulesToLoad, strictDi) {
    strictDi = (strictDi === true);
    var INSTANTIATING = {},
        providerSuffix = 'Provider',
        path = [],
        loadedModules = new HashMap([], true),
        providerCache = {
            $provide: {
                provider: supportObject(provider),
                factory: supportObject(factory),
                service: supportObject(service),
                value: supportObject(value),
                constant: supportObject(constant),
                decorator: decorator
            }
        },
        providerInjector = (providerCache.$injector =
            createInternalInjector(providerCache, function(serviceName, caller) {
                if (angular.isString(caller)) {
                    path.push(caller);
                }
                throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
            })),
        instanceCache = {},
        instanceInjector = (instanceCache.$injector =
            createInternalInjector(instanceCache, function(serviceName, caller) {
                var provider = providerInjector.get(serviceName + providerSuffix, caller);
                return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
            }));


    forEach(loadModules(modulesToLoad), function(fn) {
        instanceInjector.invoke(fn || noop);
    });

    return instanceInjector;

    ////////////////////////////////////
    // $provider
    ////////////////////////////////////

    function supportObject(delegate) {
        return function(key, value) {
            // 如果第一个参数是一个object,则用delegate函数遍历这个object
            if (isObject(key)) {
                forEach(key, reverseParams(delegate));
            }
            // 否则,用delegate函数直接访问前两个参数
            else {
                return delegate(key, value);
            }
        };
    }

    function provider(name, provider_) {
        // 如果name中没有service属性,抛出异常
        assertNotHasOwnProperty(name, 'service');
        // 实例化provider
        if (isFunction(provider_) || isArray(provider_)) {
            provider_ = providerInjector.instantiate(provider_);
        }
        // 如果实例化后的对象没有$get属性,则抛出异常。
        // $get用于定义factory方法
        if (!provider_.$get) {
            throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
        }
        // 将实例化后的对象存放在cache中,并返回这个对象
        return providerCache[name + providerSuffix] = provider_;
    }

    function enforceReturnValue(name, factory) {
        return function enforcedReturnValue() {
            var result = instanceInjector.invoke(factory, this);
            if (isUndefined(result)) {
                throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
            }
            return result;
        };
    }

    function factory(name, factoryFn, enforce) {
        return provider(name, {
            $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
        });
    }

    function service(name, constructor) {
        return factory(name, ['$injector', function($injector) {
            return $injector.instantiate(constructor);
        }]);
    }

    function value(name, val) {
        return factory(name, valueFn(val), false);
    }

    function constant(name, value) {
        assertNotHasOwnProperty(name, 'constant');
        providerCache[name] = value;
        instanceCache[name] = value;
    }

    function decorator(serviceName, decorFn) {
        var origProvider = providerInjector.get(serviceName + providerSuffix),
            orig$get = origProvider.$get;

        origProvider.$get = function() {
            var origInstance = instanceInjector.invoke(orig$get, origProvider);
            return instanceInjector.invoke(decorFn, null, {
                $delegate: origInstance
            });
        };
    }

    ////////////////////////////////////
    // Module Loading
    ////////////////////////////////////
    function loadModules(modulesToLoad) {
        var runBlocks = [],
            moduleFn;
        forEach(modulesToLoad, function(module) {
            // loadedModules:已经被load的module
            // 数据结构类似:{'ng': true, '$compile': true}
            if (loadedModules.get(module)) return;
            loadedModules.put(module, true);

            function runInvokeQueue(queue) {
                var i, ii;
                for (i = 0, ii = queue.length; i < ii; i++) {
                    var invokeArgs = queue[i],
                        provider = providerInjector.get(invokeArgs[0]);

                    provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
                }
            }

            try {
                if (isString(module)) {
                    // angularModule()函数同angular.module()函数
                    moduleFn = angularModule(module);
                    // 递归调用loadModules,获取当前module的依赖
                    runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
                    runInvokeQueue(moduleFn._invokeQueue);
                    runInvokeQueue(moduleFn._configBlocks);
                } else if (isFunction(module)) {
                    runBlocks.push(providerInjector.invoke(module));
                } else if (isArray(module)) {
                    runBlocks.push(providerInjector.invoke(module));
                } else {
                    assertArgFn(module, 'module');
                }
            } catch (e) {
                if (isArray(module)) {
                    module = module[module.length - 1];
                }
                if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
                    // Safari & FF's stack traces don't contain error.message content
                    // unlike those of Chrome and IE
                    // So if stack doesn't contain message, we create a new string that contains both.
                    // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
                    /* jshint -W022 */
                    e = e.message + '\n' + e.stack;
                }
                throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
                    module, e.stack || e.message || e);
            }
        });
        return runBlocks;
    }

    ////////////////////////////////////
    // internal Injector
    ////////////////////////////////////

    function createInternalInjector(cache, factory) {

        /**
         * 从cache中获取service
         */
        function getService(serviceName, caller) {
            // 如果cache中已经存在,则直接返回
            if (cache.hasOwnProperty(serviceName)) {
                // 如果这个service正处于实例化阶段,则抛出循环依赖的异常
                if (cache[serviceName] === INSTANTIATING) {
                    throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
                        serviceName + ' <- ' + path.join(' <- '));
                }
                return cache[serviceName];
            }
            // 否则尝试创建这个service
            else {
                try {
                    // 将service名字(或者说唯一ID)存放在path数组中
                    path.unshift(serviceName);
                    // 状态置为实例化中
                    cache[serviceName] = INSTANTIATING;
                    // 实例化并返回service,该service有个$get属性,在此处这个$get属性实际上就是caller
                    return cache[serviceName] = factory(serviceName, caller);
                } catch (err) {
                    // 实例化出现了异常,删除cache中相应的service属性
                    if (cache[serviceName] === INSTANTIATING) {
                        delete cache[serviceName];
                    }
                    throw err;
                } finally {
                    path.shift();
                }
            }
        }

        /**
         * 调用fn指示的工厂方法,并返回工厂方法的返回值
         */
        function invoke(fn, self, locals, serviceName) {
            // 参数修正
            if (typeof locals === 'string') {
                serviceName = locals;
                locals = null;
            }

            var args = [],
                $inject = annotate(fn, strictDi, serviceName),  // 解析出fn中的依赖:一个字符串数组
                length, i,
                key;

            // 找到各个依赖注入对象,并存放在args中
            for (i = 0, length = $inject.length; i < length; i++) {
                key = $inject[i];
                // 对于依赖名字不是字符串的,直接抛出异常
                if (typeof key !== 'string') {
                    throw $injectorMinErr('itkn',
                        'Incorrect injection token! Expected service name as string, got {0}', key);
                }
                args.push(
                    locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName)
                );
            }
            if (isArray(fn)) {
                fn = fn[length];
            }

            // http://jsperf.com/angularjs-invoke-apply-vs-switch
            // #5388
            // 执行工厂函数
            return fn.apply(self, args);
        }

        function instantiate(Type, locals, serviceName) {
            // Check if Type is annotated and use just the given function at n-1 as parameter
            // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
            // Object creation: http://jsperf.com/create-constructor/2
            var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
            var returnedValue = invoke(Type, instance, locals, serviceName);

            return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
        }

        return {
            invoke: invoke,
            instantiate: instantiate,
            get: getService,
            annotate: annotate,
            has: function(name) {
                return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
            }
        };
    }
}

createInjector.$$annotate = annotate;
blog comments powered by Disqus