zoukankan      html  css  js  c++  java
  • angular源码阅读3:真的,依赖注入的原理

    前面已经提到了:

    如何注册一个module。

    如何获取一个module。

    injector与module以及provider的关系。

    那么已经剩下最后一部分了,就是关于依赖是如何被注入的。

    且看下面这段代码。

    //如你所见,注册了一个moduleA
    //又如你所见,给moduleA上面注册了两个全局变量,a和b
    angular.module('moduleA',[]);
    angular.module('moduleA').constant('a',1);
    angular.module('moduleA').constant('b',2);
    
    //生成一个注射器
    var injector = createInjector('moduleA');
    
    
    //定义一个函数,接收两个数作为参数,打印两个数的和
    function add(a,b){
        console.log(a+b);
    }
    

    函数声明好了,moduleA上面也有值了,那么怎么把moduleA上面的值作为函数的参数传进来呢?熟悉angular的小伙伴肯定知道有如下几种办法:

    //第一种,给函数加$inject属性
    add.$inject=['a','b'];
    add(a,b)//3
    
    //第二种,数组形式
    ['a','b',add(a,b)];
    
    //第三种,直接用
    add(a,b);
    

    这次主要看第一种和第二种的原理,第三种的原理有点复杂,留着下一次说。

    还记得之前的那个createInjector函数么?这个函数接收一些module名称作为参数,返回一个对象就是injector对象。当createInjector运行的时候,会把所有module都遍历一次,把module的_invokeQueue里面的任务都找到相应的provider执行一下,然后把各个module里面注册的东西都保存一份。

    而依赖注入的本质就是——当你执行函数A的时候,根据函数A的$injector属性,或者函数A的数组元素(比如 ['a','b',add(a,b)]这种),去自己保存的那堆东西里面找到相应的数据,把这个数据传给函数进行调用。

    来,配上源代码,别看代码长,其实都是上一篇博客的代码,这次只往里面加了一小部分。

    //createInjector(['app1','app2'])
    //参数是一个字符串或者一个数组,内容是module名
    function createInjector(modulesToLoad) {
        //cache用来缓存一些一直可以用到的值
        var cache = {};
    
        //所有加载过的module都记录在这个对象里面
        var loadedModules = {};
    
        $provide = {
            constant: function(key, value) {
                cache[key] = value;
            }
        }
    
        //这里的foreach方法并不是一个真正能运行的foreach,能看懂就行了
        //每次APP启动的时候,injector都会按照传入的module名来遍历所有module
        //这样就可以得到所有module发布的任务,并且一一执行这些任务
        $.forEach(modulesToLoad, function loadModule(moduleName) {
            //查询一下记录,如果没有加载过,再去执行
            if (!loadedModules[moduleName]) {
                var module = window.angular.module(moduleName);
                //每加载一个模块,就操作一下loadedModules,把这个模块信息放进去
                loadedModules[moduleName]=true;
                //每次先把所有依赖的模块加载进去
                $.forEach(module.requires,loadModule);
                //然后再去调用这个模块的invokeQueue
                $.forEach(module._invokeQueue, function(invokeArgs) {
                    var method = invokeArgs[0];
                    var args = invokeArgs[1];
                    $provide[method].apply($provide, args);
                })
            }
        })
    
        //invoke方法用来调用函数
        function invoke(fn){
            //把fn.$inject这个数组里面的元素拿出来,args是一个新的数组
            var args=$.map(fn.$inject,function(token){
                return cache[token];
            })
            fn.apply(null,args);
        }
    
    
        return {
            has: function(key) {
                return cache.hasOwnProperty(key)
            },
            get: function(key) {
                return cache[key]
            },
            invoke:invoke
        }
    }
    

    看到那个invoke方法了么?来先顺一下思路,当createInjector函数加载了moduleA之后,返回了injector的时候,在自己的内部存储的cache里面已经是这样的:

    cache={
        a:1,
        b:2
    }
    

    然后给返回的injector增加了一个invoke方法,这个方法是用来调用函数的。代码里看的应该很清楚,invoke方法接收一个函数作为参数,然后去寻找这个函数的$injector属性,这个属性里发现了a,b两个值,于是就去cache里面找key值为a,b的两个值,然后成为一个数组args,在用fn.apply(null,args)调用函数,这样这两个之就成为函数的参数传进去了。  

    关于apply这个方法,简单说一句,第一个参数是函数的this值,第二个数组是函数的参数。有兴趣看源码的童鞋应该对这个不陌生,我就不啰嗦了。

    总之,这样一来,就把module里面的值注入给函数了。注意哦,调用函数是Injector这个对象调用的:

    injector.invoke(add)//调用add函数
    

    那么问题来了,既然给函数增加$inject属性可以注入依赖了,那么数组形式的调用怎么实现呢?其实这个也很简单。

    回想一下,其实依赖注入的本质是通过一个key值,从cache对象里面拿到相应的数据,对吧?

    在injector里面还有一个方法,叫做 annotate,中文意思是解剖,这个方法的作用就是把函数的依赖给解剖出来:比如这篇文章的例子,我们是通过$inject属性,把a,b两个key值拿出来的。看代码:

    //数组形式依赖注入
    ['a','b',function add(a,b){}];
    
    //直接调用的依赖注入
    function (a,b){}
    
    //annonate方法的作用就是把上面两种形式里面的a,b这两个键值拿出来
    

    其实对于数组形式依赖注入来说,键值很好拿,拆一下数组就行了。我就贴一下annotate的简单代码,很好懂的:

    function annotate(fn){
            //如果是一个数组 ['a','b',function(a,b){}]
            //返回数组最后一个函数之前的所有部分
            if(isArray(fn)){
                return fn.slice(0,fn.length-1);
            }else{
                //如果不是数组,就返回这个函数的$inject属性
                return fn.$inject;
            }
        }
    

    就是这么简单!

    好了,其实依赖注入更牛逼的是那个直接调用的依赖注入,这个篇幅比较长,我打算明天再更。

    晚安了兄弟们。欢迎留言和我交流呀 :)

      

      

      

      

  • 相关阅读:
    补番完了 来自深渊
    160CrackMe第十九Brad Soblesky.2
    MyBio小隐本记注册破解
    WDTP注册破解
    对话框和普通窗口工作方式的区别
    Win32汇编学习(11):对话框(2)
    Win32汇编学习(10):对话框(1)
    MongoDB的复制源oplog
    Windows搭建MongoDB复制集
    MangoDB的下载和安装
  • 原文地址:https://www.cnblogs.com/oukichi/p/5995121.html
Copyright © 2011-2022 走看看