hytzgroup / blog

write & read & though
0 stars 0 forks source link

# AngularJS注射器解析 #2

Open hytzgroup opened 5 years ago

hytzgroup commented 5 years ago

AngularJS注射器解析

[TOC]

概述

​ 注射器负责加载模块,分析依赖,执行工厂函数,是整个AngularJS中的核心代码。一个angular应用只有一个注射器。

实例

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>angularJS注射器解析</title>
    <script type="text/javascript" src="./angular.js"></script>
</head>
<body>
    <div>
        <button id='btnServ'>注入service</button>
        <button id='btnCtrl'>注入Controller</button>
    </div>
    <script type="text/javascript">
        var demoApp = angular.module('demoApp',[]),
            btnServ = document.getElementById('btnServ'),
            btnCtrl = document.getElementById('btnCtrl');
        demoApp.service('helloServ',[
            '$window',
            function($window){
                this.greet = function(text){
                    $window.alert(text);
                }
            }
        ]);
        demoApp.controller('helloCtrl',[
            '$rootScope', 'helloServ',
            function($rootScope, helloServ){
                $rootScope.name = '吴彦祖';
                helloServ.greet($rootScope.name+'say: 我最帅');
            }
        ]);
        // instanceInjector
        injector = angular.injector(['ng','demoApp']);
        btnServ.onclick = function(){
            var helloServ = injector.get('helloServ');
            helloServ.greet('hello world');
        };
        btnCtrl.onclick = function(){
            injector.instantiate([
                '$controller',
                function($controller){
                    $controller('helloCtrl')
                }
            ]);
        };
    </script>
</body>
</html>

注射器返回值分析

injector = angular.injector([]);
log(injector)
function log(injector){
     var result = '', item;
    for(var key in injector){
        item = injector[key];
        if(isArray(item)){
            item = "["+item.toString()+"]";
        }else{
            item = item.toString();
        }
        result += (key+' : '+item+'\n');
    }
    console.log(result)
}
function isArray(arr){
    var type = Object.prototype.toString;
    return typeof arr === 'object'&&type.call(arr) === "[object Array]"
}

通过控制台查看injector上的属性和方法:

{
    invoke : function invoke(fn, self, locals, serviceName){
        /**
         * 通过$$annotate方法分析出函数的依赖
         * 1.声明注入 ['$rootScope', 'helloServ',fn]
         * 2.推断注入 function($rootScope, helloServ)
         * 分析出依赖之后,获取instanceInjector上的实例,如果没有调用providerInjector实例化一个实例返回
         * 实例化完成,执行工厂函数
         */
    },
    instantiate : function  instantiate( Type, locals, serviceName){
        /**
         * 支持两种类型 [] 或者 fn
         * 实例化该工厂函数
         */
    },
    get : function getService(serviceName, caller) {
        /**
         * 从instanceCache中获取实例
         * 若没有查找到该实例,调用providerInjector实例化
         */
    },
    annotate : function annotate(fn, strictDi, name) {
        /**
         * 分析函数签名,获取依赖,支持两种格式
         * ['$rootScope', 'helloServ',fn]
         * function($rootScope, helloServ)
         * fn[$inject] = ['$rootScope', 'helloServ']
         */
    },
    has : function (name) {

    }
}

providerCache和instanceCache的区别

providerCache分析

//在providerCache上缓存vider
providerCache = {
    $provide:{
        provider:fn,
        factory:fn,
        service:fn,
        value:fn,
        constant:fn,
        decorator:decorator
    }
};
providerInjector = providerCache.$injector = 
    // createInternalInjector返回注射器的实例,即:
    // invoke、instantiate、get、annotate、has四个方法
    createInternalInjector(
        providerCache,
        function(serviceName, caller){
            /**
             * 判断providerCache有没有需要的serviceProvider
             * 若没有强制抛错
             */
        }
    )

通过查看createInjector的源码分析得出以下结论:

  1. controller、service、factory等参数化处理[$provider,method,arguments]后,无论什么类型最后都会通过$provide.provider注册成一个"provider",缓存在providerCache上

  2. providerInjector是providerCache.$injector的一个引用,providerInjector返回的是一个对象,其中getService方法的查找范围providerCache,回调函数是在没有找到需要的provider做抛错处理

  3. providerCache.$provide源码分析

    // providerCache.$provide控制台打印
    {
    provider : function (key, value) {
        if (isObject(key)) {
            forEach(key, reverseParams(provider));
        } else {
            return provider(key, value);
        }
    },
    factory : function (key, value) {},
    service : function (key, value) {},
    value : function (key, valueFn) {},
    constant : function (key, value) {},
    decorator : function decorator(serviceName, decorFn) {
           // 设计模式AOP
        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
            });
        };
    }
    }
    // provider源码
    function provider(name, provider_) {
    /**
     * 支持三种种类型
     * function xx(){ return {$get:fn||obj} }
     * ['$scope',function(){ return {$get:fn||obj} }]
     * {$get:fn||obj}
     */
       assertNotHasOwnProperty(name, 'service');
       if (isFunction(provider_) || isArray(provider_)) {
        // fn、[]类型需要先实例化
           provider_ = providerInjector.instantiate(provider_);
       }
    // 强制要求$get属性
       if (!provider_.$get) {
           throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
       }
    // 注册到providerCache
       return providerCache[name + providerSuffix] = provider_;
    }

instanceCache分析

// instanceCache缓存provider生成的实例
instanceCache = {};
instanceInjector = instanceCache.$injector = 
    createInternalInjector(
        providerCache,
        function(serviceName, caller){
            /**
             * 从providerCache上获取provider
             * instanceInjector初始化获取的provider
             */
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
        }
    )

通过查看createInjector的源码可以得出以下结论

  1. instanceCache缓存从provider上生成的实例。
  2. instanceInjector是 instanceCache.$injector的一个引用instanceInjector返回一个对象,其中getService方法的查找范围instanceCache,回调函数是从instanceCache上找到需要的provder,然后实例化

loadModules分析

​ 在创建完providerInjector和instanceInjector两个对象后,loadModules开始加载定义好的模块[ 'ng', ['$provide', function($provide) { $provide.value('$rootElement', element);} ], 'demoApp']

// ng模块参数化处理后
{
    name:'ng',
    requires:['ngLocale'],
    _configBlocks:[
        ['$injector', 'invoke', ['$provide',function ngModule] ]
    ],
    _invokeQueue:[],
    _runBlocks:[],
    ...
}
// ngLocale模块参数化处理后
{
    name:'ngLocale',
    requires:[],
    _configBlocks:[
        ['$injector', 'invoke', ['$provide',fn] ]
    ],
    _invokeQueue:[],
    _runBlocks:[],
    ...
}
// demoApp模块参数化处理后
{
    name:'demoApp',
    requires:[],
    _configBlocks:[],
    _invokeQueue:[
        ["$provide", "service", ['helloServ', ["$window", ƒn]] ],
        ["$controllerProvider", "register", ['helloServ', ["$rootScope", "helloServ", ƒn] ] ],

    ],
    _runBlocks:[],
    ...
}

loadModules依次递归加载模块,源码如下:

function loadModules(modulesToLoad) {
    /**
     * String类型的模块
     * 递归分析依赖,添加到runBlocks中。在providerCache中查找provider(invokeArgs[0]),执行provider对应的方法(invokeArgs[1])并传参数(invokeArgs[2])
     * Function类型 / Array类型
     * 实例化模块,添加到runBlock上
     */
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function (module) {
        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)) {
                moduleFn = angularModule(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) {}
    });
    return runBlocks;
}

总结

注射器是angularJS中的核心代码,一个angularJS应用只有一个注射器,注射器内部会创建两个对象,分别为:providerCache、instanceCache。providerCache缓存provider函数,instanceCache缓存实例。providerCache、instanceCache都有一个$injector注射器,providerCache.$injector主要是用来分析出构造函数遇到[''],然后缓存,instanceCache.$injector主要是负责实例化

hytzgroup commented 5 years ago

<!DOCTYPE html>

angularJS注射器解析