源代码解读 - DI.js 依赖输入

DI

DI 是一个依赖注入管理模块,Dgeni内部的module通过DI.js管理

Dgeni调用DI部分代码
      const dgeniModule = new di.Module()
        .value('dgeni', this)
        .factory('log', logFactory)
        .factory('getInjectables', getInjectablesFactory);

      // Create the dependency injection container, from all the packages' modules
      const modules = packages.map(pkg => pkg.module);
      modules.unshift(dgeniModule);

DI代码入口

进一步到di的源代码出口, 可以看到annotate, Module 和Injector

module.exports = {
  annotate: require('./annotation').annotate,
  Module: require('./module'),
  Injector: require('./injector')
};

annotate

annotate主要有两个函数annotate和parse

函数
  • annotate
    annotate的最后一个参数需要时注入的方法fn, 其他参数会保存在fn.$inject之内,目前还没有到看有内部使用这个方法的地方。
var annotate = function() {
  var args = Array.prototype.slice.call(arguments);
  var fn = args.pop();

  fn.$inject = args;

  return fn;
};
  • parse

parse方法会把function转化为字符串,在用正则处理参数。parse方法可以获取function的参数。

// - can't put into "function arg" comments
// function /* (no parenthesis like this) */ (){}
// function abc( /* xx (no parenthesis like this) */ a, b) {}

可以看到parse方法不支持写在funtion同行的注释,这点要注意。

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG = /\/\*([^\*]*)\*\//m;
var parse = function(fn) {
  if (typeof fn !== 'function') {
    throw new Error('Can not annotate "' + fn + '". Expected a function!');
  }

  var match = fn.toString().match(FN_ARGS);
  return match[1] && match[1].split(',').map(function(arg) {
    match = arg.match(FN_ARG);
    return match ? match[1].trim() : arg.trim();
  }) || [];
};

如果parse调用自己,即parse(parse)。那么返回的是[ "fn" ]。但是这个fn不需要时一个可以变成module的依赖,并且这个依赖可以被Inject记录。

Module

Module的代码更简单,输出一个Module类构造函数,内置一个providers存储数组。factory,value,type代码接口一样,都可以返回this用于链式调用。功能就是往providers里加入provider。最后有一个forEach函数用于遍历providers。
注意provider的形式为[name, 'factory' | 'value' | 'type', factory |value|type ]

var Module = function() {
  var providers = [];

  this.factory = function(name, factory) {
    providers.push([name, 'factory', factory]);
    return this;
  };

  this.value = function(name, value) {
    providers.push([name, 'value', value]);
    return this;
  };

  this.type = function(name, type) {
    providers.push([name, 'type', type]);
    return this;
  };

  this.forEach = function(iterator) {
    providers.forEach(iterator);
  };
};

module.exports = Module;

Injector

Injector模块使用了上面说到的Module和autoAnnotate模块,最后返回一个var Injector = function(modules, parent)Injector类。是一个管理使用依赖的模块。

构造函数

var Injector = function(modules, parent)
DI.js使用了原生的Javascript, 且没有定义原型链。所有的属性,方法和构造代码都在这个function里面。这里我们提取构造函数相关的代码部分。

instances.injector = this;
var factoryMap = {
    factory: invoke,
    type: instantiate,
    value: function(value) {
      return value;
    }
};
modules.forEach(function(module) {
    // TODO(vojta): handle wrong inputs (modules)
    if (module instanceof Module) {
      module.forEach(function(provider) {
        var name = provider[0];
        var type = provider[1];
        var value = provider[2];

        providers[name] = [factoryMap[type], value, type];
      });
    } else if (typeof module === 'object') {
      Object.keys(module).forEach(function(name) {
        var type = module[name][0];
        var value = module[name][1];

        providers[name] = [factoryMap[type], value, type];
      });
    }
});

可以看到之类的modules数组中的module可以有两种形式存在, 最终以[factoryMap方法, value, type]的格式存储在providers里。

  • Module 的instance
    构造函数这里会通过Module自身实现的forEach, 遍历其中的provider,形式为[name, 'factory' | 'value' | 'type', factory |value|type ],
  • 普通对象
    对于普通的对象,构造函数会遍历她每一个属性,然后使用属性名为name。在Dgeni的Package.ts文件的processor中可以看到这样的对象。
    if (typeof processorDef === 'function' ) {
      this.module[name] = ['factory', processorDef];
    } else {
      this.module[name] = ['value', processorDef];
    }
属性

Injector并没有外部可以调用的public属性,这里主要讨论它的private属性。

  • parent
    父节点,帮助Inject形成树形接口,方便遍历。

  • currentlyResolving
    var currentlyResolving = [];
    依赖注入的关键是,调用者不用自己创建实例,实例可以缓存在Inject中。Injector采用惰性加载的方式创建实例,如果方法被调用的时候没有创建实例,Inject会立即创建实例。在这一创建过程中,对象会被添加到currentlyResolving数组中,完成创建后则从currentlyResolving数组移除 。

  • providers
    var providers = this._providers = Object.create(parent._providers || null);
    存储provider,创建实例的时候使用,以[factoryMap方法, value, type]的格式存储。

  • instances
    var instances = this._instances = Object.create(null);
    Object.create会使用现有的对象来提供新创建的对象的proto
    存储实例

  • factoryMap

var factoryMap = {
    factory: invoke,
    type: instantiate,
    value: function(value) {
      return value;
    }
};

定义了调用依赖的方式。

  • factory调用invoke, 相当于直接调用方法
  • type 返回一个实例,相当于new了一个对象=
  • value直接返回值
方法
  • get
    var get = function(name)
    get方法会返回Instance[name], 如果没有instance有provide,怎会自动实例化一个。name还可以是a.b.c的形式。在invoke函数中,get方法被用来寻找已经记录狗的依赖。

  • instantiate

  var instantiate = function(Type) {
    var instance = Object.create(Type.prototype);
    var returned = invoke(Type, instance);

    return typeof returned === 'object' ? returned : instance;
  };

实例Type类型, 如果调用invoke能返回对象实例,则返回这个实例。不然会返回一个Object.create创造的instance。

  • invoke
   var invoke = function(fn, context) {
    if (typeof fn !== 'function') {
      throw error('Can not invoke "' + fn + '". Expected a function!');
    }

    var inject = fn.$inject && fn.$inject || autoAnnotate(fn);
    var dependencies = inject.map(function(dep) {
      return get(dep);
    });

    // TODO(vojta): optimize without apply
    return fn.apply(context, dependencies);
  };

invoke方法会查询fn的$inject,就是依赖。没有依赖的话,会通过autoAnnotate自动寻找依赖。这个时候就可以fn的参数命名和依赖一一对应。invoke使用apply返回调用结果。

  • createChild
    var createChild = function(modules, providersFromParent)
    根据当前Injector创建Injector, modules在被使用前会在前部加入将providersFromParent转化为module加入。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容