摘要:本來打算寫篇文章介紹下控制反轉的常見模式依賴注入。這就是依賴注入起作用的地方,當前來看,高效管理依賴的能力是迫切需要的,本文總結了原作者對這個問題的看法。結束語依賴注入是我們所有人都做過的事情中的一種,可能沒有意識到罷了。
本來打算寫篇文章介紹下控制反轉的常見模式-依賴注入。在翻看資料的時候,發現了一篇好文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如果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的正則表達式:/^functions*[^(]*(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使用這個正則來處理函數時,可以得到下面結果:
["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(/^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現在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我們做的就是將依賴加到作用域上,這樣的好處是不用再參數里加依賴了,已經是函數作用域的一部分了。
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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104173.html
摘要:但是,由于天生存在著一點戲劇性據傳說是在飛機上幾天時間設計出來的,模塊系統作為一門語言最基本的屬性卻是所缺的。尤其是在多頁面的項目下,不同頁面的腳本都是根據依賴關系異步按需加載的,不用手動處理每個頁面加載腳本的情況。 轉原文 概述 javaScript — 目錄最火熱的語言,到處發著光芒, html5, hybrid apps, node.js, full-stack 等等。jav...
摘要:想閱讀更多優質原創文章請猛戳博客一,跨站腳本攻擊,因為縮寫和重疊,所以只能叫??缯灸_本攻擊是指通過存在安全漏洞的網站注冊用戶的瀏覽器內運行非法的標簽或進行的一種攻擊。跨站腳本攻擊有可能造成以下影響利用虛假輸入表單騙取用戶個人信息。 前言 在互聯網時代,數據安全與個人隱私受到了前所未有的挑戰,各種新奇的攻擊技術層出不窮。如何才能更好地保護我們的數據?本文主要側重于分析幾種常見的攻擊的類型...
摘要:不過,相對于靜態類型檢查帶來的好處,這些代價是值得的。當然少不了的模塊化標準,雖然到目前為止和大部分瀏覽器都還不支持它。本身支持兩種模塊化方式,一種是對的模塊的微小擴展,另一種是在發布之前本身模仿的命名空間。有一種情況例外。 TypeScript 帶來的最大好處就是靜態類型檢查,所以在從 JavaScript 轉向 TypeScript 之前,一定要認識到添加類型定義會帶來額外的工作量...
摘要:劫持用戶是最常見的跨站攻擊形式,通過在網頁中寫入并執行腳本執行文件多數情況下是腳本代碼,劫持用戶瀏覽器,將用戶當前使用的信息發送至攻擊者控制的網站或服務器中。預防將錄入的惡意標簽進行轉碼再存儲,主要在后端錄入的時候做。 xss定義 Cross Site Scripting的縮寫本來是CSS,但是這樣就跟Cascading Style Sheets的縮寫混淆了,所以使用XSS,使用字母X...
閱讀 1433·2021-09-03 10:29
閱讀 3457·2019-08-29 16:24
閱讀 2010·2019-08-29 11:03
閱讀 1409·2019-08-26 13:52
閱讀 2925·2019-08-26 11:36
閱讀 2786·2019-08-23 17:19
閱讀 559·2019-08-23 17:14
閱讀 811·2019-08-23 13:59