# 简易依赖注入实现

# IOC、DI

IOC 是目的,DI 是手段。IOC 是指让生成类的方式由传统方式(new)反过来,既程序员不调用 new,需要类的时候由框架注入(DI),是同一件不同层面的解读

  • IOC 是 Inversion of Control 的缩写,“控制反转”

引进了第三方 IOC 容器来实现对象之间的解耦 也就是创建对象的控制权转移到了第三方的 IOC 容器

  • IOC 是通过 DI 来实现控制反转:DI 就是 IOC 的具体实现

DI 就是做如下几点的工作:

  1. 谁依赖于谁:应用程序依赖于 IOC 容器
  2. 为什么需要依赖:应用程序需要 IOC 容器提供外部资源
  3. 谁注入谁:IOC 容器向应用程序中注入
  4. 注入了什么:注入应用程序所需要的外部资源

# 依赖注入介绍

第一次听到这个说法是在 angular 的时候,我们都知道 angular 内部大量使用了依赖注入。虽然我到现在也没玩过:),不过这并不影响我们来探究一下它。

首先试图形象的说明一下(个人观点、有问题欢迎指正):有那么一群人,这群人的职业是程序员。他们除了工作不想费力气去做别的事。除了上班剩下的只有买吃的和买格子衫。具体吃什么和格子衫什么样子他们并不关心。那么也许我们可以提供一个公共服务,来专门为程序员提供吃的和格子衫。程序员不需要关心我们怎么做吃的和去哪里买格子衫,他们只需要告诉我们他们需要就可以了,买好之后我们自然会给他们送到。这样就可以避免每个程序员还要花费心思独自的去吃东西和买格子衫,省去了大把时间就可以更好的投入到工作中了。

刚刚那段说法可以抽象为下面这张简易示意图:

avatar

按照上面图的流程中我们可以知道我们需要实现这么几件事:

  • 提供一个服务容器
  • 为目标函数注册需要的依赖
  • 获取目标函数注册的依赖项
  • 通过依赖项来查询对应服务
  • 将获取的依赖项传入目标函数

# 依赖注入实现

  1. 提供一个服务容器

    //假装提供一些服务
    var services = {
      A: () => {
        console.log(1)
      },
      B: () => {
        console.log(2)
      },
      C: () => {
        console.log(3)
      }
    }
    
  2. 为目标函数注册需要的依赖;

    // 目标函数
    function Service(A, B) {
      A()
      B()
    }
    

    目前的注册方式采用在形参的方式来传递,我们不需要关心 A、B 是怎么实现的,我们只需要知道这些代表着吃的和格子衫就可以了:)

  3. 获取目标函数注册的依赖项

    // 获取 func 的参数列表(依赖列表)
    getFuncParams = function (func) {
      var matches = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)
      if (matches && matches.length > 1)
        return matches[1].replace(/\s+/, '').split(',')
      return []
    }
    

    实现原理为将传入的目标函数进行正则匹配,匹配出形参。这其中的关键点在于这段正则表达式:

    ;/^function\s*[^\(]*\(\s*([^\)]*)\)/m
    

    其中(\s*([^)]* 通过括号来提取匹配到 function 后面参数括号的内部内容,也就是可以得到参数字符串。这里面是运用了括号的提取数据的规则来获取的信息,规则如下:

    var regex = /(\d{4})-(\d{2})-(\d{2})/
    var string = '2017-06-12'
    console.log(string.match(regex))
    // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
    

    结果数组中第一个元素为匹配结果,之后为括号内的数据,由此我们便可知道,这段正则通过括号的使用,获取到了整个形参作为一个字符串,之后再通过 split 进行拆分就得到了我们想要的结果。

  4. 通过依赖项来查询对应服务

    //简易实现
    setFuncParams = function (params) {
      for (var i in params) {
        params[i] = services[params[i]]
      }
      return params
    } //依次对应服务中的项进行查找返回结果。
    
  5. 将获取的依赖项传入目标函数

    // 注射器
    function Activitor(func, scope) {
      return () => {
        func.apply(scope || {}, setFuncParams(getFuncParams(func)))
      }
    }
    // 实例化 Service 并调用方法
    var service = Activitor(Service)
    service() //1 2
    

# 小结

至此我们便完整地实现了一个很简单的依赖注入的模式,源码在这里 (opens new window)。非常简单,同时也没有做很多的判断。不过核心的思路还是梳理了出来。自己闷头琢磨了半天,有不对的地方欢迎斧正~

PS:下面的几篇参考资料写的都很好,其中颜海镜 (opens new window)老师的 JavaScript 里的依赖注入 (opens new window)很有深意,拜读了很久,也分享给大家。

# 参考资料