欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript中的依賴注入詳解

 更新時(shí)間:2015年03月18日 10:13:00   投稿:junjie  
這篇文章主要介紹了JavaScript中的依賴注入詳解,本文講解了requirejs/AMD方法、反射(reflection)方法等內(nèi)容,需要的朋友可以參考下

計(jì)算機(jī)編程的世界其實(shí)就是一個(gè)將簡(jiǎn)單的部分不斷抽象,并將這些抽象組織起來(lái)的過(guò)程。JavaScript也不例外,在我們使用JavaScript編寫應(yīng)用時(shí),我們是不是都會(huì)使用到別人編寫的代碼,例如一些著名的開(kāi)源庫(kù)或者框架。隨著我們項(xiàng)目的增長(zhǎng),我們需要依賴的模塊變得越來(lái)越多,這個(gè)時(shí)候,如何有效的組織這些模塊就成了一個(gè)非常重要的問(wèn)題。依賴注入解決的正是如何有效組織代碼依賴模塊的問(wèn)題。你可能在一些框架或者庫(kù)種聽(tīng)說(shuō)過(guò)“依賴注入”這個(gè)詞,比如說(shuō)著名的前端框架AngularJS,依賴注入就是其中一個(gè)非常重要的特性。但是,依賴注入根本就不是什么新鮮玩意,它在其他的編程語(yǔ)言例如PHP中已經(jīng)存在已久。同時(shí),依賴注入也沒(méi)有想象種那樣復(fù)雜。在本文中,我們將一起來(lái)學(xué)習(xí)JavaScript中的依賴注入的概念,深入淺出的講解如何編寫“依賴注入風(fēng)格”的代碼。

目標(biāo)設(shè)定

假設(shè)我們現(xiàn)在擁有兩個(gè)模塊。第一個(gè)模塊的作用是發(fā)送Ajax請(qǐng)求,而第二個(gè)模塊的作用則是用作路由。

復(fù)制代碼 代碼如下:

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

這時(shí),我們編寫了一個(gè)函數(shù),它需要使用上面提到的兩個(gè)模塊:
復(fù)制代碼 代碼如下:

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

在這里,為了讓我們的代碼變得有趣一些,這個(gè)參數(shù)需要多接收幾個(gè)參數(shù)。當(dāng)然,我們完全可以使用上面的代碼,但是無(wú)論從哪個(gè)方面來(lái)看上面的代碼都略顯得不那么靈活。要是我們需要使用的模塊名稱變?yōu)镾erviceXML或者ServiceJSON該怎么辦?或者說(shuō)如果我們基于測(cè)試的目的想要去使用一些假的模塊改怎么辦。這時(shí),我們不能僅僅去編輯函數(shù)本身。因此我們需要做的第一件事情就是將依賴的模塊作為參數(shù)傳遞給函數(shù),代碼如下所示:
復(fù)制代碼 代碼如下:

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

在上面的代碼中,我們完全傳遞了我們所需要的模塊。但是這又帶來(lái)了一個(gè)新的問(wèn)題。假設(shè)我們?cè)诖a的哥哥部分都調(diào)用了doSomething方法。這時(shí),如果我們需要第三個(gè)依賴項(xiàng)該怎么辦。這個(gè)時(shí)候,去編輯所有的函數(shù)調(diào)用代碼并不是一個(gè)明智的方法。因此,我們需要一段代碼來(lái)幫助我們做這件事情。這就是依賴注入器試圖去解決的問(wèn)題?,F(xiàn)在我們可以來(lái)定下我們的目標(biāo)了:

1.我們應(yīng)該能夠去注冊(cè)依賴項(xiàng)
2.依賴注入器應(yīng)該接收一個(gè)函數(shù),然后返回一個(gè)能夠獲取所需資源的函數(shù)
3.代碼不應(yīng)該復(fù)雜,而應(yīng)該簡(jiǎn)單友好
4.依賴注入器應(yīng)該保持傳遞的函數(shù)作用域
5.傳遞的函數(shù)應(yīng)該能夠接收自定義的參數(shù),而不僅僅是被描述的依賴項(xiàng)

requirejs/AMD方法

或許你已經(jīng)聽(tīng)說(shuō)過(guò)了大名鼎鼎的requirejs,它是一個(gè)能夠很好的解決依賴注入問(wèn)題的庫(kù):

復(fù)制代碼 代碼如下:

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

requirejs的思想是首先我們應(yīng)該去描述所需要的模塊,然后編寫你自己的函數(shù)。其中,參數(shù)的順序很重要。假設(shè)我們需要編寫一個(gè)叫做injector的模塊,它能夠?qū)崿F(xiàn)類似的語(yǔ)法。
復(fù)制代碼 代碼如下:

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");

在繼續(xù)往下之前,需要說(shuō)明的一點(diǎn)是在doSomething的函數(shù)體中我們使用了expect.js這個(gè)斷言庫(kù)來(lái)確保代碼的正確性。這里有一點(diǎn)類似TDD(測(cè)試驅(qū)動(dòng)開(kāi)發(fā))的思想。

現(xiàn)在我們正式開(kāi)始編寫我們的injector模塊。首先它應(yīng)該是一個(gè)單體,以便它能夠在我們應(yīng)用的各個(gè)部分都擁有同樣的功能。

復(fù)制代碼 代碼如下:

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

    }
}


這個(gè)對(duì)象非常的簡(jiǎn)單,其中只包含兩個(gè)函數(shù)以及一個(gè)用于存儲(chǔ)目的的變量。我們需要做的事情是檢查deps數(shù)組,然后在dependencies變量種尋找答案。剩余的部分,則是使用.apply方法去調(diào)用我們傳遞的func變量:
復(fù)制代碼 代碼如下:

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)));
    }       
}

如果你需要指定一個(gè)作用域,上面的代碼也能夠正常的運(yùn)行。

在上面的代碼中,Array.prototype.slice.call(arguments, 0)的作用是將arguments變量轉(zhuǎn)換為一個(gè)真正的數(shù)組。到目前為止,我們的代碼可以完美的通過(guò)測(cè)試。但是這里的問(wèn)題是我們必須要將需要的模塊寫兩次,而且不能夠隨意排列順序。額外的參數(shù)總是排在所有的依賴項(xiàng)之后。

反射(reflection)方法

根據(jù)維基百科中的解釋,反射(reflection)指的是程序可以在運(yùn)行過(guò)程中,一個(gè)對(duì)象可以修改自己的結(jié)構(gòu)和行為。在JavaScript中,簡(jiǎn)單來(lái)說(shuō)就是閱讀一個(gè)對(duì)象的源碼并且分析源碼的能力。還是回到我們的doSomething方法,如果你調(diào)用doSomething.toString()方法,你可以獲得下面的字符串:

復(fù)制代碼 代碼如下:

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

這樣一來(lái),只要使用這個(gè)方法,我們就可以輕松的獲取到我們想要的參數(shù),以及更重要的一點(diǎn)就是他們的名字。這也是AngularJS實(shí)現(xiàn)依賴注入所使用的方法。在AngularJS的代碼中,我們可以看到下面的正則表達(dá)式:
復(fù)制代碼 代碼如下:

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

我們可以將resolve方法修改成如下所示的代碼:

復(fù)制代碼 代碼如下:

resolve: function() {
    var func, deps, scope, args = [], self = this;
    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);
    }       
}

我們使用上面的正則表達(dá)式去匹配我們定義的函數(shù),我們可以獲取到下面的結(jié)果:

復(fù)制代碼 代碼如下:

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

此時(shí),我們只需要第二項(xiàng)。但是一旦我們?nèi)コ硕嘤嗟目崭癫⒁?來(lái)切分字符串以后,我們就得到了deps數(shù)組。下面的代碼就是我們進(jìn)行修改的部分:
復(fù)制代碼 代碼如下:

var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());

在上面的代碼中,我們遍歷了依賴項(xiàng)目,如果其中有缺失的項(xiàng)目,如果依賴項(xiàng)目中有缺失的部分,我們就從arguments對(duì)象中獲取。如果一個(gè)數(shù)組是空數(shù)組,那么使用shift方法將只會(huì)返回undefined,而不會(huì)拋出一個(gè)錯(cuò)誤。到目前為止,新版本的injector看起來(lái)如下所示:

復(fù)制代碼 代碼如下:

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");

在上面的代碼中,我們可以隨意混淆依賴項(xiàng)的順序。

但是,沒(méi)有什么是完美的。反射方法的依賴注入存在一個(gè)非常嚴(yán)重的問(wèn)題。當(dāng)代碼簡(jiǎn)化時(shí),會(huì)發(fā)生錯(cuò)誤。這是因?yàn)樵诖a簡(jiǎn)化的過(guò)程中,參數(shù)的名稱發(fā)生了變化,這將導(dǎo)致依賴項(xiàng)無(wú)法解析。例如:

復(fù)制代碼 代碼如下:

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

因此我們需要下面的解決方案,就像AngularJS中那樣:
復(fù)制代碼 代碼如下:

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

}]);


這和最一開(kāi)始看到的AMD的解決方案很類似,于是我們可以將上面兩種方法整合起來(lái),最終代碼如下所示:
復(fù)制代碼 代碼如下:

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);
        }       
    }
}

這一個(gè)版本的resolve方法可以接受兩個(gè)或者三個(gè)參數(shù)。下面是一段測(cè)試代碼:

復(fù)制代碼 代碼如下:

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');
});
doSomething("Other");

你可能注意到了兩個(gè)逗號(hào)之間什么都沒(méi)有,這并不是錯(cuò)誤。這個(gè)空缺是留給Other這個(gè)參數(shù)的。這就是我們控制參數(shù)順序的方法。

結(jié)語(yǔ)

在上面的內(nèi)容中,我們介紹了幾種JavaScript中依賴注入的方法,希望本文能夠幫助你開(kāi)始使用依賴注入這個(gè)技巧,并且寫出依賴注入風(fēng)格的代碼。

相關(guān)文章

  • JavaScript來(lái)實(shí)現(xiàn)打開(kāi)鏈接頁(yè)面的簡(jiǎn)單實(shí)例

    JavaScript來(lái)實(shí)現(xiàn)打開(kāi)鏈接頁(yè)面的簡(jiǎn)單實(shí)例

    下面小編就為大家?guī)?lái)一篇JavaScript來(lái)實(shí)現(xiàn)打開(kāi)鏈接頁(yè)面的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-06-06
  • js實(shí)現(xiàn)內(nèi)容顯示并使用json傳輸數(shù)據(jù)

    js實(shí)現(xiàn)內(nèi)容顯示并使用json傳輸數(shù)據(jù)

    這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)內(nèi)容顯示并使用json傳輸數(shù)據(jù)的方法,感興趣的小伙伴們可以參考一下
    2016-03-03
  • 微信小程序如何修改本地緩存key中單個(gè)數(shù)據(jù)的詳解

    微信小程序如何修改本地緩存key中單個(gè)數(shù)據(jù)的詳解

    這篇文章主要介紹了微信小程序如何修改本地緩存key中單個(gè)數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 基于JavaScript實(shí)現(xiàn)五子棋游戲

    基于JavaScript實(shí)現(xiàn)五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了基于JavaScript實(shí)現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • JavaScript的詞法結(jié)構(gòu)精華篇

    JavaScript的詞法結(jié)構(gòu)精華篇

    今天小編就為大家分享一篇關(guān)于JavaScript的詞法結(jié)構(gòu)精華篇,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10
  • 高性能js數(shù)組去重(12種方法,史上最全)

    高性能js數(shù)組去重(12種方法,史上最全)

    數(shù)組去重,一般都是在面試的時(shí)候才會(huì)碰到,一般是要求手寫數(shù)組去重方法的代碼。如果是被提問(wèn)到,數(shù)組去重的方法有哪些?你能答出其中的10種,面試官很有可能對(duì)你刮目相看
    2019-12-12
  • IE6,IE7,IE8下使用Javascript記錄光標(biāo)選中范圍(已補(bǔ)全)

    IE6,IE7,IE8下使用Javascript記錄光標(biāo)選中范圍(已補(bǔ)全)

    IE6,7,8下使用Javascript記錄光標(biāo)選中范圍(已補(bǔ)全)(已解決單個(gè)節(jié)點(diǎn)內(nèi)部重復(fù)字符的問(wèn)題)
    2011-08-08
  • js使用formData實(shí)現(xiàn)批量上傳

    js使用formData實(shí)現(xiàn)批量上傳

    這篇文章主要為大家詳細(xì)介紹了js使用formData實(shí)現(xiàn)批量上傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • 利用d3.js實(shí)現(xiàn)蜂巢圖表帶動(dòng)畫效果

    利用d3.js實(shí)現(xiàn)蜂巢圖表帶動(dòng)畫效果

    這篇文章主要給大家介紹了關(guān)于如何利用d3.js實(shí)現(xiàn)蜂巢圖表帶動(dòng)畫效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用d3.js具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 小程序兩種滾動(dòng)公告欄的實(shí)現(xiàn)方法

    小程序兩種滾動(dòng)公告欄的實(shí)現(xiàn)方法

    這篇文章主要介紹了小程序兩種滾動(dòng)公告欄的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09

最新評(píng)論