源代码解读 - 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加入。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容