zoukankan      html  css  js  c++  java
  • 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 + '
    ' + e.stack;
                        }
                        throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:
    {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方法,对页面上的元素节点上的指令进行编译操作。

    加油!

  • 相关阅读:
    iOS添加Google语言识别功能
    转载:Ajax中get与post请求详解
    SSH:Spring+Spring MVC+hibernate整合开发笔记
    IDEA中创建maven web项目的详细部署步骤
    C++实现RTMP协议发送H.264编码及AAC编码的音视频
    C++ 勘误
    Postgresql ODBC驱动,用sqlserver添加dblink跨库访问postgresql数据库
    .Net NPOI 上传excel文件、提交后台获取excel里的数据
    .Net NPOI 根据excel模板导出excel、直接生成excel
    .Net 登陆的时候添加验证码
  • 原文地址:https://www.cnblogs.com/chaojidan/p/4267846.html
Copyright © 2011-2022 走看看