javascript中的依靠注入【转】

原本盘算写篇文章引见下掌握反转的罕见情势-依靠注入。在翻看材料的时刻,发现了一篇好文Dependency injection in JavaScript,就不本身折腾了,连系本身明白翻译一下,好文共赏。

我喜好援用如许一句话‘编程是对庞杂性的治理’。能够你也听过计算机天下是一个庞大的笼统构造。我们简朴的包装东西并反复的临盆新的东西。思索那末一下下,我们运用的编程言语都包含内置的功用,这些功用多是基于其他初级操纵的笼统要领,包含我们是用的javascript。
早晚,我们都邑须要运用别的开辟者开辟的笼统功用,也就是我们要依靠其他人的代码。
我愿望运用没有依靠的模块,明显这是很难完成的。纵然你创建了很好的像黑盒一样的组件,但总有个将一切部份兼并起来的处所。
这就是依靠注入起作用的处所,当前来看,高效治理依靠的才是迫切须要的,本文总结了原作者对这个题目的意见。

  • 目的

假定我们有两个模块,一个是发出ajax要求的效劳,一个是路由:

var service = function() {
    return { name: 'Service' };
}
var router = function() {
    return { name: 'Router' };
}

下面是另一个依靠了上述模块的函数:

var doSomething = function(other) {
    var s = service();
    var r = router();
};

为了更风趣一点,该函数须要接收一个参数。固然我们能够运用上面的代码,然则这不太天真。
如果我们想运用ServiceXML、ServiceJSON,或许我们想要mock一些测试模块,如许我们不能每次都是编辑函数体。
为了处置惩罚这个近况,起首我们提出将依靠当作参数传给函数,以下:

var doSomething = function(service, router, other) {
    var s = service();
    var r = router();
};

如许,我们把须要的模块的详细实例通报过来。
但是如许有个新的题目:想一下如果dosomething函数在许多处所被挪用,如果有第三个依靠前提,我们不能转变一切的挪用doSomething的处所。
举个小栗子:
如果我们有许多处所用到了doSomething:

//a.js
var a = doSomething(service,router,1)
//b.js 
var b = doSomething(service,router,2)
// 如果依靠前提更改了,即doSomething须要第三个依靠,才一般事变
// 这时刻就须要在上面差别文件中修正了,如果文件数目够多,就不适宜了。
var doSomething = function(service, router, third,thother) {
    var s = service();
    var r = router();
    //***
};

因而,我们须要一个协助我们来治理依靠的东西。这就是依靠注入器想要处置惩罚的题目,先看一下我们想要到达的目的:

能够注册依靠
注入器应当接收一个函数而且返回一个已取得须要资本的函数
我们不应当写庞杂的代码,须要简短文雅的语法
注入器应当坚持传入函数的作用域
被传入的函数应当能够接收自定义参数,不仅仅是被形貌的依靠。
看起来比较圆满的列表就如上了,让我们来尝试完成它。

requirejs/AMD的体式格局
人人都能够听说过requirejs,它是很不错的依靠治理计划。

define(['service', 'router'], function(service, router) {       
    // ...
});

这类思绪是起首声明须要的依靠,然后最先编写函数。这里参数的递次是很重要的。我们来尝尝写一个名为injector的模块,能够接收雷同语法。

var doSomething = injector.resolve(['service', 'router'], function(service, router, other) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

这里轻微停留一下,解释一下doSomething的函数体,运用expect.js来作为断言库来确保我的代码能像希冀那样一般事变。表现了一点点TDD(测试驱动开辟)的开辟情势。

下面是我们injector模块的最先,一个单例情势是很好的挑选,因而能够在我们运用的差别部份运转的很不错。

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {

    }
}

从代码来看,确切是一个很简朴的对象。有两个函数和一个作为存储行列的变量。
我们须要做的是搜检deps依靠数组,而且从dependencies行列中查找答案。剩下的就是挪用.apply要领来拼接被通报过来函数的参数。

//处置惩罚以后将依靠项当作参数传入给func

resolve: function(deps, func, scope) {
    var args = [];
    //处置惩罚依靠,如果依靠行列中不存在对应的依靠模块,明显该依靠不能被挪用那末报错,
    for(var i=0; i<deps.length, d=deps[i]; i++) {
        if(this.dependencies[d]) {
            args.push(this.dependencies[d]);
        } else {
            throw new Error('Can\'t resolve ' + d);
        }
    }
    //处置惩罚参数,将参数拼接在依靠背面,以便和函数中参数位置对应
    return function() {
        func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
    }        
}

如果scope存在,是能够被有用通报的。Array.prototype.slice.call(arguments, 0)将arguments(类数组)转换成真正的数组。
如今来看很不错的,能够经由过程测试。当前的题目时,我们必需写两次须要的依靠,而且递次不可更改,分外的参数只能在最背面。

  • 反射完成

从维基百科来讲,反射是顺序在运转时能够搜检和修正对象构造和行动的一种才。
简而言之,在js的高低文中,是指读取而且剖析对象或许函数的源码。看下开首的doSomething,如果运用doSomething.toString() 能够获得下面的效果。

function (service, router, other) {
    var s = service();
    var r = router();
}

这类将函数转成字符串的体式格局给予我们猎取预期参数的才。而且更重要的是,他们的name。
下面是Angular依靠注入的完成体式格局,我从Angular那拿了点能够猎取arguments的正则表达式:

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

如许我们能够修正resolve要领了:

  • tip

这里,我将测试例子拿上来应当更好明白一点。

var doSomething = injector.resolve(function(service, other, router) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

继承来看我们的完成。

resolve: function() {
    // agrs 传给func的参数数组,包含依靠模块及自定义参数
    var func, deps, scope, args = [], self = this;
    // 猎取传入的func,重要是为了下面来拆分字符串
    func = arguments[0];
    // 正则拆分,猎取依靠模块的数组
    deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(',');
    //待绑定作用域,不存在则不指定
    scope = arguments[1] || {};
    return function() {
        // 将arguments转为数组
        // 即背面再次挪用的时刻,doSomething("Other");   
        // 这里的Other就是a,用来补充缺失的模块。
        var a = Array.prototype.slice.call(arguments, 0);
        //轮回依靠模块数组
        for(var i=0; i<deps.length; i++) {
            var d = deps[i];
            // 依靠行列中模块存在且不为空的话,push进参数数组中。
            // 依靠行列中不存在对应模块的话从a中取第一个元素push进去(shift以后,数组在转变)
            args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
        }
        //依靠当作参数传入
        func.apply(scope || {}, args);
    }        
}

运用这个正则来处置惩罚函数时,能够获得下面效果:

["function (service, router, other)", "service, router, other"]

我们须要的只是第二项,一旦我们消灭数组并拆分字符串,我们将会获得依靠数组。重要变化在下面:

var a = Array.prototype.slice.call(arguments, 0);

args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());

如许我们就轮回遍历依靠项,如果缺乏某些东西,我们能够尝试从arguments对象中猎取。
幸亏,当数组为空的时刻shift要领也只是返回undefined而非抛错。所以新版的用法以下:

//不必在前面声明依靠模块了

var doSomething = injector.resolve(function(service, other, router) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

如许就不必反复声清楚明了,递次也可变。我们复制了Angular的魔力。
但是,这并不圆满,紧缩会损坏我们的逻辑,这是反射注入的一大题目。由于紧缩转变了参数的称号所以我们没有才去处置惩罚这些依靠。比方:

// 明显依据key来婚配就是有题目的了

var doSomething=function(e,t,n){var r=e();var i=t()}

Angular团队的处置惩罚计划以下:

var doSomething = injector.resolve([‘service’, ‘router’, function(service, router) {

}]);

看起来就和最先的require.js的体式格局一样了。作者个人不能找到更优的处置惩罚计划,为了顺应这两种体式格局。终究计划看起来以下:

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function() {
        var func, deps, scope, args = [], self = this;
        // 该种状况是兼容情势,先声明
        if(typeof arguments[0] === 'string') {
            func = arguments[1];
            deps = arguments[0].replace(/ /g, '').split(',');
            scope = arguments[2] || {};
        } else {
            // 反射的第一种体式格局
            func = arguments[0];
            deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
            scope = arguments[1] || {};
        }
        return function() {
            var a = Array.prototype.slice.call(arguments, 0);
            for(var i=0; i<deps.length; i++) {
                var d = deps[i];
                args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
            }
            func.apply(scope || {}, args);
        }        
    }
}

如今resolve接收两或许三个参数,如果是两个就是我们写的第一种了,如果是三个,会将第一个参数剖析并添补到deps。
下面就是测试例子(我一向以为将这段例子放在前面能够人人更好浏览一些。):

// 缺失了一项模块other
var doSomething = injector.resolve('router,,service', function(a, b, c) {
    expect(a().name).to.be('Router');
    expect(b).to.be('Other');
    expect(c().name).to.be('Service');
});
// 这里传的Other将会用来拼集
doSomething("Other");

能够会注意到argumets[0]中确切了一项,就是为了测试添补功用的。

直接注入作用域
有时刻,我们运用第三种的注入体式格局,它涉及到函数作用域的操纵(或许其他名字,this对象),并不常常运用

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {
        var args = [];
        scope = scope || {};
        for(var i=0; i<deps.length, d=deps[i]; i++) {
            if(this.dependencies[d]) {
                //区分就在这里了,直接将依靠加到scope上
                //如许就能够直接在函数作用域中挪用了
                scope[d] = this.dependencies[d];
            } else {
                throw new Error('Can\'t resolve ' + d);
            }
        }
        return function() {
            func.apply(scope || {}, Array.prototype.slice.call(arguments, 0));
        }        
    }
}

我们做的就是将依靠加到作用域上,如许的优点是不必再参数里加依靠了,已是函数作用域的一部份了。

var doSomething = injector.resolve(['service', 'router'], function(other) {
    expect(this.service().name).to.be('Service');
    expect(this.router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

结束语
依靠注入是我们一切人都做过的事变中的一种,能够没有意想到罢了。纵然没有听过,你也能够用过许屡次了。
经由过程这篇文章关于这个熟习而又生疏的观点的相识加深了不少,愿望能协助到有须要的同砚。末了个人才有限,翻译有误的处所迎接人人指出,共同进步。
再次谢谢原文作者原文地点

如水穿石,厚积才可薄发

原文链接:https://www.cnblogs.com/pqjwy…

    原文作者:槐破梦
    原文地址: https://segmentfault.com/a/1190000019090166
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞