首頁 > web前端 > js教程 > JavaScript中的依賴注入詳解_javascript技巧

JavaScript中的依賴注入詳解_javascript技巧

WBOY
發布: 2016-05-16 16:08:51
原創
1102 人瀏覽過

電腦程式的世界其實就是一個將簡單的部分不斷抽象,並將這些抽象組織起來的過程。 JavaScript也不例外,當我們使用JavaScript編寫應用程式時,我們是否都會使用到別人編寫的程式碼,例如一些著名的開源程式庫或框架。隨著我們專案的成長,我們需要依賴的模組變得越來越多,這個時候,如何有效的組織這些模組就成了一個非常重要的問題。依賴注入解決的正是如何有效組織程式碼依賴模組的問題。你可能在某些框架或庫種聽過「依賴注入」這個詞,比如說著名的前端框架AngularJS,依賴注入就是其中一個非常重要的特性。但是,依賴注入根本就不是什麼新鮮玩意,它在其他的程式語言例如PHP中已經存在已久。同時,依賴注入也沒有想像種那麼複雜。在本文中,我們將一起來學習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該怎麼辦?或者說如果我們基於測試的目的想要去使用一些假的模組改怎麼辦。這時,我們不能只是去編輯函數本身。因此我們需要做的第一件事就是將依賴的模組作為參數傳遞給函數,程式碼如下所示:
複製程式碼 程式碼如下:

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

在上面的程式碼中,我們完全傳遞了我們所需要的模組。但這又帶來了一個新的問題。假設我們在程式碼的哥哥部分都呼叫了doSomething方法。這時,如果我們需要第三個依賴項該怎麼辦。這個時候,去編輯所有的函數呼叫程式碼並不是一個明智的方法。因此,我們需要一段程式碼來幫助我們做這件事。這就是依賴注入器試圖去解決的問題。現在我們可以來設定我們的目標了:

1.我們應該能夠去註冊依賴項
2.依賴注入器應該接收一個函數,然後回傳一個能夠取得所需資源的函數
3.程式碼不應該複雜,而應該簡單友善
4.依賴注入器應該保持傳遞的函數作用域
5.傳遞的函數應該能夠接收自訂的參數,而不僅僅是被描述的依賴項

requirejs/AMD方法

或許你已經聽過大名鼎鼎的requirejs,它是一個能夠很好的解決依賴注入問題的函式庫:

複製程式碼 程式碼如下:

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

requirejs的想法是首先我們應該去描述所需的模組,然後寫出你自己的函數。其中,參數的順序很重要。假設我們需要寫一個叫做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         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)));
    }       
}

如果你需要指定一個作用域,上面的程式碼也能夠正常的運作。

在上面的程式碼中,Array.prototype.slice.call(arguments, 0)的作用是將arguments變數轉換為一個真正的陣列。到目前為止,我們的程式碼可以完美的通過測試。但是這裡的問題是我們必須要將需要的模組寫兩次,而且不能夠隨意排列順序。額外的參數總是排在所有的依賴項之後。

反射(reflection)方法

根據維基百科中的解釋,反射(reflection)指的是程式可以在運作過程中,一個物件可以修改自己的結構和行為。在JavaScript中,簡單來說就是閱讀一個物件的原始碼並且分析原始碼的能力。還是回到我們的doSomething方法,如果你呼叫doSomething.toString()方法,你可以得到下面的字串:

複製程式碼 程式碼如下:

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

這樣一來,只要使用這個方法,我們就可以輕鬆的取得到我們想要的參數,以及更重要的一點就是他們的名字。這也是AngularJS實作依賴注入所使用的方法。在AngularJS的程式碼中,我們可以看到下面的正規表示式:
複製程式碼 程式碼如下:

/^functions*[^(]*(s*([^)]*))/m

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

複製程式碼 程式碼如下:

resolve: function() {
    var func, deps, scope, args = [], self = this;
    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             var d = deps[i];
            args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
        }
        func.apply(scope || {}, args);
    }       
}

我們使用上面的正規表示式去匹配我們定義的函數,我們可以得到下面的結果:

複製程式碼 程式碼如下:

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

此時,我們只需要第二項。但是一旦我們去除了多餘的空格並以,來切分字串以後,我們就得到了deps數組。下面的程式碼就是我們進行修改的部分:
複製程式碼 程式碼如下:

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

在上面的程式碼中,我們遍歷了依賴項目,如果其中有缺失的項目,如果依賴項目中有缺失的部分,我們就從arguments物件中取得。如果一個陣列是空數組,那麼使用shift方法將只會傳回undefined,而不會拋出一個錯誤。到目前為止,新版本的injector看起來如下圖:

複製程式碼 程式碼如下:

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

在上面的程式碼中,我們可以隨意混淆依賴項的順序。

但是,沒有什麼是完美的。反射方法的依賴注入存在一個非常嚴重的問題。當程式碼簡化時,會發生錯誤。這是因為在程式碼簡化的過程中,參數的名稱發生了變化,這將導致依賴項無法解析。例如:

複製程式碼 程式碼如下:

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

因此我們需要下面的解決方案,就像AngularJS中:
複製程式碼 程式碼如下:

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

}]);


這和最一開始看到的AMD的解決方案很類似,於是我們可以將上面兩種方法整合起來,最終程式碼如下:
複製程式碼 程式碼如下:

var 注入器 = {
    依賴項:{},
    註冊:函數(鍵,值){
        this.dependency[key] = value;
    },
    解:函數(){
        var func, deps, 範圍, args = [], self = this;
        if(typeof argument[0] === '字串') {
            func = 參數[1];
            deps = argument[0].replace(/ /g, '').split(',');
            範圍=參數[2] || {};
        } 其他 {
            func = 參數[0];
deps = func.toString().match(/^functions*[^(]*(s*([^)]*))/m)[1].replace(/ /g, '').split(' ,');
            範圍=參數[1] || {};
        }
        回傳函數() {
            var a = Array.prototype.slice.call(arguments, 0);
            for(var i=0; i                 var d = deps[i];
                args.push(self.dependency[d] && d != '' ? self.dependency[d] : a.shift());
            }
            func.apply(scope || {}, args);
        }       
    }
}

這個版本的resolve方法可以接受兩個或三個參數。以下是一段測試程式碼:

複製程式碼以下程式碼:

var doSomething = Injector.resolve('路由器,,服務', function(a, b, c) {
    Expect(a().name).to.be('路由器');
    Expect(b).to.be('其他');
    Expect(c().name).to.be('Service');
});
doSomething("其他");

你可能注意到什麼都沒有之間有兩個逗號,這不是錯誤。這個空缺是刪除其他這個參數的。這就是我們控制參數順序的方法。

結語

在上面的內容中,我們介紹了幾種JavaScript中依賴注入的方法,希望本文能幫助你開始使用依賴注入這個技巧,並寫出依賴注入風格的程式碼。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板