AngularJS源码解析2:注入器的详解

上一课,没有讲createInjector方法,只是讲了它的主要作用,这一课,详细来讲一下这个方法。此方法,最终返回的注册器实例对象有以下几个方法:

invoke,
instantiate,
get,
annotate,
has。

function createInjector(modulesToLoad) {

        var INSTANTIATING = {},

            providerSuffix = 'Provider',

            path = [],

            loadedModules = new HashMap(),    //一个hash对象,用来存储已经加载过的模块

            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() {    //创建一个内部的注册器实例对象,它跟下面的注册器实例对象拥有相同的方法,只是它们操作的参数不一样,这个操作的是providerCache和抛出错误的回调函数。

                    throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));

                })),

            instanceCache = {},

            instanceInjector = (instanceCache.$injector =

                createInternalInjector(instanceCache, function(servicename) {     //创建一个内部的注册器实例对象,并返回此注册器实例对象,此对象有几个方法,它的这几个方法里面有引用你通过createInternalInjector传进去的参数,因此,形成了闭包。

                    var provider = providerInjector.get(servicename + providerSuffix);

                    return instanceInjector.invoke(provider.$get, provider);

                })

        );    

        forEach(loadModules(modulesToLoad),     //加载初始化的模块

        function(fn) { instanceInjector.invoke(fn || noop); }

     );

        return instanceInjector;   //返回注册器实例对象

        function supportObject(delegate) {

            return function(key, value) {

                if (isObject(key)) {

                    forEach(key, reverseParams(delegate));

                } else {

                    return delegate(key, value);

                }

            };

        }

        function provider(name, provider_) {

            assertNotHasOwnProperty(name, 'service');

            if (isFunction(provider_) || isArray(provider_)) {

                provider_ = providerInjector.instantiate(provider_);

            }

            if (!provider_.$get) {

                throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);

            }

            return providerCache[name + providerSuffix] = provider_;

        }

        function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }

        function service(name, constructor) {

            return factory(name, ['$injector', function($injector) {

                return $injector.instantiate(constructor);

            }]);

        }

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

        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});

            };

        }

        function loadModules(modulesToLoad){    //这里的modulesToLoad = ["ng", ["$provide",function($provide){ $provide.value("$rootElement", element); }]]。 

            var runBlocks = [], moduleFn, invokeQueue, i, ii;

            forEach(modulesToLoad, function(module) {    //针对每一个模块,执行此方法

                if (loadedModules.get(module)) return;    //如果此模块已经加载过了,就不用再次加载了,直接进行下一次循环去加载下一个模块

                loadedModules.put(module, true);

                try {

                    if (isString(module)) {    //ng模块进入到这里   

                        moduleFn = angularModule(module);   //得到ng模块的实例对象

                        runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);

                        for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {

                            var invokeArgs = invokeQueue[i],

                                provider = providerInjector.get(invokeArgs[0]);

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

                        }

                    } 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;

        }

        function createInternalInjector(cache, factory) {    //创建一个内部的注入器实例对象

            function getService(serviceName) {

                if (cache.hasOwnProperty(serviceName)) {

                    if (cache[serviceName] === INSTANTIATING) {

                        throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));

                    }

                    return cache[serviceName];

                } else {

                    try {

                        path.unshift(serviceName);

                        cache[serviceName] = INSTANTIATING;

                        return cache[serviceName] = factory(serviceName);

                    } catch (err) {

                        if (cache[serviceName] === INSTANTIATING) {

                            delete cache[serviceName];

                        }

                        throw err;

                    } finally {

                        path.shift();

                    }

                }

            }

            function invoke(fn, self, locals){

                var args = [],

                    $inject = annotate(fn),

                    length, i,

                    key;

                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)

                    );

                }

                if (!fn.$inject) {

                    // this means that we must be an array.

                    fn = fn[length];

                }

                // http://jsperf.com/angularjs-invoke-apply-vs-switch

                // #5388

                return fn.apply(self, args);

            }

            function instantiate(Type, locals) {

                var Constructor = function() {},

                    instance, returnedValue;

                // 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) {}]);

                Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;

                instance = new Constructor();

                returnedValue = invoke(Type, instance, locals);

                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);

                }

            };

        }

  }

首先,在这个函数体内部,有几个创建服务提供者(provider)的几个方法:provider,service,factory,value。反观这几个方法,它们内部的实现调用的都是provider方法,而且所有的provider都必须包含一个$get属性,因此你自定义一个provider时,必须有$get属性。当然,如果你通过service,factory等方法创建的话,你不需要显式的写$get属性,因为这些方法的内部实现都会增加一个$get属性。

然后,在函数体内部,除了创建provider的方法外,还有一个创建注入器的方法createInternalInjector,返回的注入器主要用来创建provider真正的实例对象,并注入给你的应用,同时处理相关的依赖创建。

然后,在函数体的内部,有几个内部变量,一个是providerCache,这个会保存一个$provide属性对象,此属性对象主要用来对外提供创建服务提供者(provider)的方法,同时,providerCache会保存所有已经注入进来的provider(在publishExternalAPI方法中,有一段这样的代码)。

 

 $provide.provider({          //在ng模块中,定义一系列的服务提供者

                    $animate: $AnimateProvider,                

                    $controller: $ControllerProvider,

                    $filter: $FilterProvider,

                    $http: $HttpProvider,

                    $location: $LocationProvider,

                    $parse: $ParseProvider,

                    $rootScope: $RootScopeProvider,

                    $window: $WindowProvider

 });

 

同时,providerCache还有一个$injector属性对象,此属性对象就是一个注入器实例。它跟另一个变量providerInjector指向的是同一个注入器实例对象。

instanceCache这个变量,会保存所有已经实例化的provider对象,同时,这个变量的$injector属性值是一个注入器实例。它跟另外一个变量instanceInjector指向的是同一个注入器实例对象,这个注入器实例对象是用来真正实例化一个provider的。

然后,在函数体内部,有一个loadModules方法,angularjs就是依靠这个方法来加载模块,以及模块所依赖的provider。loadModules函数体中,会依次加载模块数组里对应的provider(forEach方法遍历数组),如果模块数组中的值是字符串,就会直接创建provider实例对象。如果不是,就会把此模块的provider信息push到数组runBlocks中,然后通过instanceInjector来进行实例化操作。

在创建provider实例对象时,代码如下:

var invokeArgs = invokeQueue[i],provider = providerInjector.get(invokeArgs[0]);



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

上面代码的意思是:通过注入器得到对应的服务提供者provider,然后调用服务提供者的实例化方法,创建这个服务的实例对象。

instanceInjector和providerInjector这两个变量都是注入器实例对象,它们之间的区别在于调用createInternalInjector方法创建它们时,传入的第二个参数。当要真正的实例化provider的时候,第二个参数负责真正的初始化providerCache的保存的provider,其实就是执行provider的$get方法,然后把值保存到instanceCache中,之所以用缓存对象保存实例出来的provider对象,是为了实现单例操作。providerInjector注入器,传入的第二个参数是一个空函数,不会实例化provider对象,而instanceInjector注入器,传入的第二个参数是一个实例化provider对象的函数。

function(servicename) {

                    var provider = providerInjector.get(servicename + providerSuffix);

                    return instanceInjector.invoke(provider.$get, provider);

}

上面的代码就是创建instanceInjector注入器时,传入的第二个参数,这个函数的意思是:先在providerCache里查找此服务提供者provider,然后把此provider的$get方法传给instanceInjector的invoke方法,最后生成这个provider的实例对象。

最后,讲一下注入器的invoke方法:

function invoke(fn, self, locals){

                var args = [],

                    $inject = annotate(fn),

                    length, i,

                    key;



                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)

                    );

                }

                if (!fn.$inject) {

                    // this means that we must be an array.

                    fn = fn[length];

                }



                // http://jsperf.com/angularjs-invoke-apply-vs-switch

                // #5388

                return fn.apply(self, args);

}

此函数体内,会调用一个方法annotate,此方法是获取一个函数的参数,并以数组形式返回。invoke会自动检查要执行的函数的参数,假如这些参数已经生成实例对象了,就传给要执行的函数,否则先生成依赖的服务的实例对象,然后传给要执行的函数。

bootstrap方法中,创建注入器的代码执行结束后,就会调用注入器的invoke方法:

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',

                function(scope, element, compile, injector, animate) {

                    scope.$apply(function() {

                        element.data('$injector', injector);

                        compile(element)(scope);

                    });

                }]

            );

return injector;

invoke方法,就会生成数组中'$rootScope', '$rootElement', '$compile', '$injector', '$animate'的实例对象,传入回调函数function中,回调函数执行时,就可以调用这些服务实例对象了。在回调函数中,就会调用angular的compile方法对页面上的指令进行编译操作了。

最后,我们总结下angular初始化的过程:

在页面上加载angularJS库,浏览器进行下载,下载完成后,执行。angularJS源代码是一个立即执行函数,因此执行此立即执行函数,这样就不会污染全局环境。

然后,初始化一个window.angular = {},定义一系列的对象和方法。然后绑定jQuery,如果页面上有引入jQuery,那么angular.element = jQuery,否则就用angular内部定义的JQLite赋给angular.element。

然后,调用publishExternalAPI方法,扩展angular对象的属性方法,然后创建模块加载器,也就是angular.module方法,此模块加载器可以创建模块的实例对象。然后利用此模块加载器,加载ng等内置模块,然后此ng模块中,创建$compile服务提供者,然后定义一系列的内置指令,以及一系列的内置服务提供者。

最后调用

 jqLite(document).ready(function() {

        angularInit(document, bootstrap);

 }

此方法angularInit会等到文档加载完成后才会执行,以防页面没加载完成,你就开始编译页面上的元素节点上的指令。

此方法,会查找页面上是否有ng-app指令,如果有,就进行angular应用的启动操作:执行bootstrap方法。

然后,bootstrap方法会调用内部的定义的doBootstrap方法。

doBootstrap首先调用createInjector方法创建注入器,同时,createInjector方法也会加载angular内置的服务提供者和模块,以及实例化一些服务提供者对象等等。

然后调用注入器的invoke方法,来实例化服务的实例对象,然后注入到回调函数中去。

最后,执行回调函数,此回调函数中,会调用compile方法,对页面上的元素节点上的指令进行编译操作。

 

 

 

加油!

你可能感兴趣的:(AngularJS)