js依賴注入初探
- 2019 年 12 月 4 日
- 筆記
本文作者:IMWeb coolriver 原文出處:IMWeb社區 未經同意,禁止轉載
前言:一個題目
之前在codewars上做在線題目時遇到這樣一個問題:實現一個依賴注入的「注射器」。當時對依賴注入這一概念還不是很理解,只是根據題目的要求初步認識了依賴注入。題目的要求如下:
/** * Constructor DependencyInjector * @param {Object} - object with dependencies */ var DI = function (dependency) { this.dependency = dependency; }; // Should return new function with resolved dependencies DI.prototype.inject = function (func) { // Your code goes here }題目先簡單地創建了一個依賴注入器的構造函數,題目挑戰的方法是在inject函數中編寫程式碼,實現依賴注入。評價挑戰成功與否的測試如下:
// 要注入的依賴 var deps = { 'dep1': function () {return 'this is dep1';}, 'dep2': function () {return 'this is dep2';}, 'dep3': function () {return 'this is dep3';}, 'dep4': function () {return 'this is dep4';} }; // 新建一個「注射器」 var di = new DI(deps); // 注射 var myFunc = di.inject(function (dep3, dep1, dep2) { return [dep1(), dep2(), dep3()].join(' -> '); }); // 測試 Test.assertEquals(myFunc(), 'this is dep1 -> this is dep2 -> this is dep3');先來看一下我之前實現的方案:
/** * Constructor DependencyInjector * @param {Object} - object with dependencies */ var DI = function (dependency) { this.dependency = dependency; }; // Should return new function with resolved dependencies DI.prototype.inject = function (func) { // Your code goes here var dependency = this.dependency; //返回的函數 return function(){ var funArr = [], newParm = []; // 解析函數參數 var parmsArr = func.toString().replace(/^function(?:s|r|n)*(([^)]*))(?:.|r|n)*{(?:.|r|n)*}$/,"$1").split(/s*,s*/); console.log(parmsArr); var obj = {}; //根據參數查找依賴 for (var key in dependency){ if (parmsArr.indexOf(key) >= 0){ obj[key] = dependency[key]; newParm.push(key); } } // 待注入的依賴函數定義 for (var key in obj){ //console.log("var "+key+" = " + obj[key].toString()+";"); funArr.push("var "+key+" = " + obj[key].toString()+";"); } // 注入依賴函數定義 eval("var newfunc="+func.toString().replace(/^(function(?:.|r|n)*{)((?:.|r|n)*)(})$/,"$1"+funArr.join('')+"$2$3")); console.log(newfunc.toString()); // 執行注入依賴後的函數 return newfunc.apply(obj,newParm); } }這是較早的時候寫的程式碼,不是很優雅,暴力地將依賴函數的定義注入到函數體中。下面看看codewars上面的大神們寫的,根據網站對solution排序的最佳方案:
/** * Constructor DependencyInjector * @param {Object} - object with dependencies */ var DI = function (dependency) { this.dependency = dependency; }; // Should return new function with resolved dependencies DI.prototype.inject = function (func) { var deps = /^[^(]+(([^)]+)/.exec(func.toString()); // 構建參數綁定數組 deps = deps ? deps[1] .split(/s?,s?/) .map(function (dep) { return this.dependency[dep]; }.bind(this)) : []; // 通過apply將依賴參數傳入函數 return function () { return func.apply(this, deps); }; }看完上面的方案才發現我之前的方案是多麼麻煩。。。
依賴注入是什麼?
在解決上面是上的問題後,回過頭來想:依賴注入是啥?其實通過題目的描述以及測試程式碼容易理解到,依賴注入可以動態地為函數添加依賴。依賴注入在強類型語言中,如JAVA,比較常見,是一種解藕的方式。 對於如果解釋和理解依賴注入,在看了一些「百科」和程式碼後仍然不是很清晰。後來找到了stackoverflow上的一個問題:如何向一個5歲的小孩解釋依賴注入。被認同得最多的答案原文如下:
When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn't want you to have. You might even be looking for something we don't even have or which has expired. What you should be doing is stating a need, "I need something to drink with lunch," and then we will make sure you have something when you sit down to eat.
基本的意思是:熊孩子不會自己弄吃的,或者吃到不該吃的,比如這樣:

熊孩子只需要告訴家長「我餓了,要吃東西」,家長知道了就給熊孩子準備適合他吃的:

在js中依賴注入的概念不像java中被經常提到,主要原因是在js中很容易就實現了這種動態依賴。最簡單的例子:bind函數。js可以通過bind,apply,call等函數可以很方便地控制函數的參數和this變數,所以簡單地依賴注入在很多情況下已經被不知不覺地使用。在AMD的模組定義中,其方式也是一種依賴注入。