摘要:本來打算寫篇文章介紹下控制反轉(zhuǎn)的常見模式依賴注入。這就是依賴注入起作用的地方,當(dāng)前來看,高效管理依賴的能力是迫切需要的,本文總結(jié)了原作者對這個問題的看法。結(jié)束語依賴注入是我們所有人都做過的事情中的一種,可能沒有意識到罷了。
本來打算寫篇文章介紹下控制反轉(zhuǎn)的常見模式-依賴注入。在翻看資料的時候,發(fā)現(xiàn)了一篇好文Dependency injection in JavaScript,就不自己折騰了,結(jié)合自己理解翻譯一下,好文共賞。
我喜歡引用這樣一句話‘編程是對復(fù)雜性的管理’??赡苣阋猜犨^計(jì)算機(jī)世界是一個巨大的抽象結(jié)構(gòu)。我們簡單的包裝東西并重復(fù)的生產(chǎn)新的工具。思考那么一下下,我們使用的編程語言都包括內(nèi)置的功能,這些功能可能是基于其他低級操作的抽象方法,包括我們是用的javascript。
遲早,我們都會需要使用別的開發(fā)者開發(fā)的抽象功能,也就是我們要依賴其他人的代碼。
我希望使用沒有依賴的模塊,顯然這是很難實(shí)現(xiàn)的。即使你創(chuàng)建了很好的像黑盒一樣的組件,但總有個將所有部分合并起來的地方。
這就是依賴注入起作用的地方,當(dāng)前來看,高效管理依賴的能力是迫切需要的,本文總結(jié)了原作者對這個問題的看法。
目標(biāo)
假設(shè)我們有兩個模塊,一個是發(fā)出ajax請求的服務(wù),一個是路由:
var service = function() { return { name: "Service" }; } var router = function() { return { name: "Router" }; }
下面是另一個依賴了上述模塊的函數(shù):
var doSomething = function(other) { var s = service(); var r = router(); };
為了更有趣一點(diǎn),該函數(shù)需要接受一個參數(shù)。當(dāng)然我們可以使用上面的代碼,但是這不太靈活。
如果我們想使用ServiceXML、ServiceJSON,或者我們想要mock一些測試模塊,這樣我們不能每次都是編輯函數(shù)體。
為了解決這個現(xiàn)狀,首先我們提出將依賴當(dāng)做參數(shù)傳給函數(shù),如下:
var doSomething = function(service, router, other) { var s = service(); var r = router(); };
這樣,我們把需要的模塊的具體實(shí)例傳遞過來。
然而這樣有個新的問題:想一下如果dosomething函數(shù)在很多地方被調(diào)用,如果有第三個依賴條件,我們不能改變所有的調(diào)用doSomething的地方。
舉個小栗子:
假如我們有很多地方用到了doSomething:
//a.js var a = doSomething(service,router,1) //b.js var b = doSomething(service,router,2) // 假如依賴條件更改了,即doSomething需要第三個依賴,才能正常工作 // 這時候就需要在上面不同文件中修改了,如果文件數(shù)量夠多,就不合適了。 var doSomething = function(service, router, third,thother) { var s = service(); var r = router(); //*** };
因此,我們需要一個幫助我們來管理依賴的工具。這就是依賴注入器想要解決的問題,先看一下我們想要達(dá)到的目標(biāo):
可以注冊依賴
注入器應(yīng)該接受一個函數(shù)并且返回一個已經(jīng)獲得需要資源的函數(shù)
我們不應(yīng)該寫復(fù)雜的代碼,需要簡短優(yōu)雅的語法
注入器應(yīng)該保持傳入函數(shù)的作用域
被傳入的函數(shù)應(yīng)該可以接受自定義參數(shù),不僅僅是被描述的依賴。
看起來比較完美的列表就如上了,讓我們來嘗試實(shí)現(xiàn)它。
requirejs/AMD的方式
大家都可能聽說過requirejs,它是很不錯的依賴管理方案。
define(["service", "router"], function(service, router) { // ... });
這種思路是首先聲明需要的依賴,然后開始編寫函數(shù)。這里參數(shù)的順序是很重要的。我們來試試寫一個名為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的函數(shù)體,使用expect.js來作為斷言庫來確保我的代碼能像期望那樣正常工作。體現(xiàn)了一點(diǎn)點(diǎn)TDD(測試驅(qū)動開發(fā))的開發(fā)模式。
下面是我們injector模塊的開始,一個單例模式是很好的選擇,因此可以在我們應(yīng)用的不同部分運(yùn)行的很不錯。
var injector = { dependencies: {}, register: function(key, value) { this.dependencies[key] = value; }, resolve: function(deps, func, scope) { } }
從代碼來看,確實(shí)是一個很簡單的對象。有兩個函數(shù)和一個作為存儲隊(duì)列的變量。
我們需要做的是檢查deps依賴數(shù)組,并且從dependencies隊(duì)列中查找答案。剩下的就是調(diào)用.apply方法來拼接被傳遞過來函數(shù)的參數(shù)。
//處理之后將依賴項(xiàng)當(dāng)做參數(shù)傳入給func
resolve: function(deps, func, scope) { var args = []; //處理依賴,如果依賴隊(duì)列中不存在對應(yīng)的依賴模塊,顯然該依賴不能被調(diào)用那么報(bào)錯, for(var i=0; i如果scope存在,是可以被有效傳遞的。Array.prototype.slice.call(arguments, 0)將arguments(類數(shù)組)轉(zhuǎn)換成真正的數(shù)組。
目前來看很不錯的,可以通過測試。當(dāng)前的問題時,我們必須寫兩次需要的依賴,并且順序不可變動,額外的參數(shù)只能在最后面。反射實(shí)現(xiàn)
從維基百科來說,反射是程序在運(yùn)行時可以檢查和修改對象結(jié)構(gòu)和行為的一種能力。
簡而言之,在js的上下文中,是指讀取并且分析對象或者函數(shù)的源碼??聪麻_頭的doSomething,如果使用doSomething.toString() 可以得到下面的結(jié)果。function (service, router, other) { var s = service(); var r = router(); }這種將函數(shù)轉(zhuǎn)成字符串的方式賦予我們獲取預(yù)期參數(shù)的能力。并且更重要的是,他們的name。
下面是Angular依賴注入的實(shí)現(xiàn)方式,我從Angular那拿了點(diǎn)可以獲取arguments的正則表達(dá)式:/^functions*[^(]*(s*([^)]*))/m這樣我們可以修改resolve方法了:
tip
這里,我將測試?yán)幽蒙蟻響?yīng)該更好理解一點(diǎn)。
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");繼續(xù)來看我們的實(shí)現(xiàn)。
resolve: function() { // agrs 傳給func的參數(shù)數(shù)組,包括依賴模塊及自定義參數(shù) var func, deps, scope, args = [], self = this; // 獲取傳入的func,主要是為了下面來拆分字符串 func = arguments[0]; // 正則拆分,獲取依賴模塊的數(shù)組 deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, "").split(","); //待綁定作用域,不存在則不指定 scope = arguments[1] || {}; return function() { // 將arguments轉(zhuǎn)為數(shù)組 // 即后面再次調(diào)用的時候,doSomething("Other"); // 這里的Other就是a,用來補(bǔ)充缺失的模塊。 var a = Array.prototype.slice.call(arguments, 0); //循環(huán)依賴模塊數(shù)組 for(var i=0; i使用這個正則來處理函數(shù)時,可以得到下面結(jié)果:
["function (service, router, other)", "service, router, other"]我們需要的只是第二項(xiàng),一旦我們清除數(shù)組并拆分字符串,我們將會得到依賴數(shù)組。主要變化在下面:
var a = Array.prototype.slice.call(arguments, 0);...
args.push(self.dependencies[d] && d != "" ? self.dependencies[d] : a.shift());這樣我們就循環(huán)遍歷依賴項(xiàng),如果缺少某些東西,我們可以嘗試從arguments對象中獲取。
幸好,當(dāng)數(shù)組為空的時候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");這樣就不用重復(fù)聲明了,順序也可變。我們復(fù)制了Angular的魔力。
然而,這并不完美,壓縮會破壞我們的邏輯,這是反射注入的一大問題。因?yàn)閴嚎s改變了參數(shù)的名稱所以我們沒有能力去解決這些依賴。例如:// 顯然根據(jù)key來匹配就是有問題的了
var doSomething=function(e,t,n){var r=e();var i=t()}Angular團(tuán)隊(duì)的解決方案如下:
var doSomething = injector.resolve(["service", "router", function(service, router) {
}]);
看起來就和開始的require.js的方式一樣了。作者個人不能找到更優(yōu)的解決方案,為了適應(yīng)這兩種方式。最終方案看起來如下:
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(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, "").split(","); scope = arguments[1] || {}; } return function() { var a = Array.prototype.slice.call(arguments, 0); for(var i=0; i現(xiàn)在resolve接受兩或者三個參數(shù),如果是兩個就是我們寫的第一種了,如果是三個,會將第一個參數(shù)解析并填充到deps。
下面就是測試?yán)?我一直認(rèn)為將這段例子放在前面可能大家更好閱讀一些。):// 缺失了一項(xiàng)模塊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]中確實(shí)了一項(xiàng),就是為了測試填充功能的。
直接注入作用域
有時候,我們使用第三種的注入方式,它涉及到函數(shù)作用域的操作(或者其他名字,this對象),并不經(jīng)常使用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我們做的就是將依賴加到作用域上,這樣的好處是不用再參數(shù)里加依賴了,已經(jīng)是函數(shù)作用域的一部分了。
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");結(jié)束語
依賴注入是我們所有人都做過的事情中的一種,可能沒有意識到罷了。即使沒有聽過,你也可能用過很多次了。
通過這篇文章對于這個熟悉而又陌生的概念的了解加深了不少,希望能幫助到有需要的同學(xué)。最后個人能力有限,翻譯有誤的地方歡迎大家指出,共同進(jìn)步。
再次感謝原文作者原文地址如水穿石,厚積才可薄發(fā)
原文鏈接:https://www.cnblogs.com/pqjwy...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/104173.html
摘要:但是,由于天生存在著一點(diǎn)戲劇性據(jù)傳說是在飛機(jī)上幾天時間設(shè)計(jì)出來的,模塊系統(tǒng)作為一門語言最基本的屬性卻是所缺的。尤其是在多頁面的項(xiàng)目下,不同頁面的腳本都是根據(jù)依賴關(guān)系異步按需加載的,不用手動處理每個頁面加載腳本的情況。 轉(zhuǎn)原文 概述 javaScript — 目錄最火熱的語言,到處發(fā)著光芒, html5, hybrid apps, node.js, full-stack 等等。jav...
摘要:想閱讀更多優(yōu)質(zhì)原創(chuàng)文章請猛戳博客一,跨站腳本攻擊,因?yàn)榭s寫和重疊,所以只能叫。跨站腳本攻擊是指通過存在安全漏洞的網(wǎng)站注冊用戶的瀏覽器內(nèi)運(yùn)行非法的標(biāo)簽或進(jìn)行的一種攻擊。跨站腳本攻擊有可能造成以下影響利用虛假輸入表單騙取用戶個人信息。 前言 在互聯(lián)網(wǎng)時代,數(shù)據(jù)安全與個人隱私受到了前所未有的挑戰(zhàn),各種新奇的攻擊技術(shù)層出不窮。如何才能更好地保護(hù)我們的數(shù)據(jù)?本文主要側(cè)重于分析幾種常見的攻擊的類型...
摘要:不過,相對于靜態(tài)類型檢查帶來的好處,這些代價(jià)是值得的。當(dāng)然少不了的模塊化標(biāo)準(zhǔn),雖然到目前為止和大部分瀏覽器都還不支持它。本身支持兩種模塊化方式,一種是對的模塊的微小擴(kuò)展,另一種是在發(fā)布之前本身模仿的命名空間。有一種情況例外。 TypeScript 帶來的最大好處就是靜態(tài)類型檢查,所以在從 JavaScript 轉(zhuǎn)向 TypeScript 之前,一定要認(rèn)識到添加類型定義會帶來額外的工作量...
摘要:從前發(fā)送請求后需等待并收到響應(yīng),才能發(fā)送下一個請求。第二次握手接收到包后就返回一個,隨機(jī)數(shù),包以及一個自己的包,然后等待的回復(fù),進(jìn)入狀態(tài)已接收狀態(tài)。 1.Http協(xié)議 1.1 Http結(jié)構(gòu)圖 showImg(https://segmentfault.com/img/bVbsrzu?w=1240&h=702); 1.2 什么是Http協(xié)議 HTTP協(xié)議是Hyper Text Transf...
摘要:劫持用戶是最常見的跨站攻擊形式,通過在網(wǎng)頁中寫入并執(zhí)行腳本執(zhí)行文件多數(shù)情況下是腳本代碼,劫持用戶瀏覽器,將用戶當(dāng)前使用的信息發(fā)送至攻擊者控制的網(wǎng)站或服務(wù)器中。預(yù)防將錄入的惡意標(biāo)簽進(jìn)行轉(zhuǎn)碼再存儲,主要在后端錄入的時候做。 xss定義 Cross Site Scripting的縮寫本來是CSS,但是這樣就跟Cascading Style Sheets的縮寫混淆了,所以使用XSS,使用字母X...
閱讀 1448·2021-09-03 10:29
閱讀 3470·2019-08-29 16:24
閱讀 2041·2019-08-29 11:03
閱讀 1425·2019-08-26 13:52
閱讀 2937·2019-08-26 11:36
閱讀 2800·2019-08-23 17:19
閱讀 571·2019-08-23 17:14
閱讀 821·2019-08-23 13:59