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

電腦程式的世界其實就是一個將簡單的部分不斷抽象,並將這些抽象組織起來的過程。 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中依賴注入的方法,希望本文能幫助你開始使用依賴注入這個技巧,並寫出依賴注入風格的程式碼。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具