隨著AngularJS的流行,依賴注入開始在JavaScript領域獲得不少的關注。 DI最突出的好處在於開發可重複使用可測試的程式碼單元。 本文以簡易的程式碼解釋DI的實作機制,更多DI優缺點的討論可參考: 何時應該使用依賴注入 一文。
每個模組宣告自己的依賴,並提供自己的服務。例如:
di.service('foo', ['bar'], function foo(bar){ function Foo(){ this.bar = bar; } this.prototype.greeting = function(){ console.log('hello, world'); } return Foo; }); var foo = di.container.get('foo'); foo.greeting();
注意依賴注入和CommonJS(或AMD)的區別, foo 只需要宣告其依賴項 bar 而不需要主動取得。 正是這一點使得 function foo 對依賴所處的位置和建置方法都完全無知, function foo 成為可測試、可重複使用的程式碼單元。
註冊服務和使用服務應該在不同時期進行。 作為一種特殊的依賴解決工具,DI框架將軟體單元的生命週期分為註冊階段和運行階段。 在上述範例中,在註冊階段提供 foo 和 bar 服務,在運作階段取得並使用這些服務。 多數DI框架都採取lazy construction的策略,該策略也避免了在註冊階段進行建構的困難。
服務的客製化可以在註冊階段後運行階段前進行。 AngularJS 1 引入配置階段來客製化這些服務,其Provider可以理解為一個特化的工廠物件。 BottleJS 則使用修飾器和中介軟體來支援對服務的客製化。
使用IoC容器來索引服務實例或儲存服務提供者。 當有人提供服務時就把它加入容器中, 當有人使用服務時就從容器中尋找提供者並產生一個服務實例。 通常服務的實例可以被快取。
先來實作最常見的介面函數 .service() ,該介面用來註冊一個服務的建構器。 被傳入的函數將會被進行 new 操作。
var di = { container: {} }; di.service = function(name, Constructor) { defineLazyProperty(name, () => new Constructor()); }; function defineLazyProperty(name, getter){ Object.defineProperty(di.container, name, { configurable: true, get: function() { var obj = getter(container); Object.defineProperty(di.container, name, { configurable: false value: obj }); return obj; } }); }
Object.defineProperty 在這裡用來做服務快取。 只在第一次建置服務時呼叫構造器,後續的存取就是直接讀取IoC容器的屬性。 它是ES5的標準方法 相容性非常好 。 有了defineLazyProperty() 方法,這些常用的註冊介面實作就很直觀了:
di.factory = function(name, factory) { return defineLazyProperty(name, factory); }; di.provider = function(name, Provider) { return defineLazyProperty(name, function(){ var provider = new Provider(); return provider.$get(); }); }; di.value = function(name, val) { return defineLazyProperty(name, () => val); };
服務的客製化介面就不再贅述了,值得一提的是統一的服務客製化需要統一的服務建構方法, 而不是直接呼叫.defineLazyProperty() 生成屬性。 AngularJS 中這些策略都由Provider來實現, 其他的所有服務註冊方法都藉由Provider來實現。
以上就是JavaScript 依賴注入實現的內容,更多相關內容請關注PHP中文網(m.sbmmt.com)!