chaojidan

导航

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方法,对页面上的元素节点上的指令进行编译操作。

 

 

 

加油!

posted on 2015-02-11 18:12  chaojidan  阅读(1628)  评论(0编辑  收藏  举报