JavaScript中实现DI的原理
在JavaScript中实现DI,看起来难,实际上原理很简单,它的核心技术是Function对象的toString()。我们都知道,对一个函数对象执行toString(),它的返回值是函数的源码,知道了这一点,接下来就简单的:我获取了函数源码,然后我对函数的声明进行解析,伪码如下:
var giveMe = function(config) { }; var registry = {}; var inject = function(func, thisForFunc) { // 获取源码 var source = func.toString(); // 用正则表达式解析源码 var matcher = source.match(/..表达式有些复杂,省略../); // 解析结果是各个参数的名称 var objectIds = .... // 查阅出相应的对象,放到数组中准备作为参数传过去 var objects = []; for (var i = 0; i < objectIds.length; ++i) objects.push(registry[objectIds[i]]); // 调用这个函数,并且把参数传过去 func.apply(thisForFunc || func, objects) }; inject(giveMe)
当然,一个实际的DI系统需要考虑的问题比这要多很多,但是这段代码用来表现原理应该足够了。
接下来我们再来看Angular中的DI实现:
在Angular中,所有主要编程元素都需要通过某种方式注册进去,比如myModule.service(‘serviceName‘, function().... 这实际上就是把后面这个函数加入到一个容器中,要注意的是:angular全面实现了延迟初始化,也就是说,当这个对象没有被别人需要的时候,它是不会被创建的,这样对于提高性能有一定的帮助,特别是加快了启动速度。
这里一个有趣的问题是:Angular的容器是什么。Angular不存在真正的全局对象,所以你可以放心的在同一个页面中放多个app,而不用担心他们互相干扰,但是容器又需要一个众所周知的地方来存放这些“名字和对象”的注册表(Registry),在Angular中,这个注册表就叫做module,所以,现在你应该知道为什么module的地位很重要了吧?不过一个app中可以存在很多不同名字的module,它们之间存在某些依赖关系,而这体现在module的声明语法中:angular.module(‘someModule‘, [‘dep1‘, ‘dep2]),这样划分module有利于程序的文件组织。
根据DI的原理,一个自然的推论就是:被注入的对象通常都是单例,因为创建了一个,就可以始终使用它了,不需要多次创建。因此,如果你需要在angular中跨controller共享数据或者通讯,那么你可以创建一个service/value/constant等,然后把它们分别注入到两个controller中,而这两个controller将自然而然的共享同一个对象。