好程序員解析Web前端中的IoC是什麼,今天要爲你們分享的文章就是關於對web前端中的 IoC的解釋。Web前端技術愈來愈火,前端應用在不斷壯大的過程當中,內部模塊間的依賴可能也會隨之愈來愈複雜,模塊間的 低複用性 致使應用 難以維護,不過咱們能夠藉助計算機領域的一些優秀的編程理念來必定程度上解決這些問題,下面咱們就來一塊兒看一看IoC。
web前端中的 IoC是什麼?
1、什麼是IoC
IoC 的全稱叫作 Inversion of Control,可翻譯爲爲「控制反轉」或「依賴倒置」,它主要包含了三個準則:
一、高層次的模塊不該該依賴於低層次的模塊,它們都應該依賴於抽象
二、抽象不該該依賴於具體實現,具體實現應該依賴於抽象
三、面向接口編程 而不要面向實現編程
概念老是抽象的,因此下面將以一個例子來解釋上述的概念:
假設須要構建一款應用叫 App,它包含一個路由模塊 Router 和一個頁面監控模塊 Track,一開始可能會這麼實現:
// app.js前端
import Router from './modules/Router';程序員
import Track from './modules/Track';web
class App {編程
constructor(options) {app
this.options = options;函數
this.router = new Router();測試
this.track = new Track();this
this.init();翻譯
}code
init() {
window.addEventListener('DOMContentLoaded', () => {
this.router.to('home');
this.track.tracking();
this.options.onReady();
});
}
}
// index.js
import App from 'path/to/App';
ew App({
onReady() {
// do something here...
},
});
嗯,看起來沒什麼問題,可是實際應用中需求是很是多變的,可能須要給路由新增功能(好比實現 history 模式)或者更新配置(啓用 history, ew Router({ mode: 'history' }))。這就不得不在 App 內部去修改這兩個模塊,這是一個 INNER BREAKING 的操做,而對於以前測試經過了的 App 來講,也必須從新測試。
很明顯,這不是一個好的應用結構,高層次的模塊 App 依賴了兩個低層次的模塊 Router 和 Track,對低層次模塊的修改都會影響高層次的模塊 App。那麼如何解決這個問題呢,解決方案就是接下來要講述的 依賴注入(Dependency Injection)。
2、依賴注入
所謂的依賴注入,簡單來講就是把高層模塊所依賴的模塊經過傳參的方式把依賴「注入」到模塊內部,上面的代碼能夠經過依賴注入的方式改形成以下方式:
// app.js
class App {
constructor(options) {
this.options = options;
this.router = options.router;
this.track = options.track;
this.init();
}
init() {
window.addEventListener('DOMContentLoaded', () => {
this.router.to('home');
this.track.tracking();
this.options.onReady();
});
}
}
// index.js
import App from 'path/to/App';
import Router from './modules/Router';
import Track from './modules/Track';
ew App({
router: new Router(),
track: new Track(),
onReady() {
// do something here...
},
});
能夠看到,經過依賴注入解決了上面所說的 INNER BREAKING 的問題,能夠直接在 App 外部對各個模塊進行修改而不影響內部。
是否是就萬事大吉了?理想很豐滿,但現實倒是很骨感的,沒過兩天產品就給你提了一個新需求,給 App 添加一個分享模塊 Share。這樣的話又回到了上面所提到的 INNER BREAKING 的問題上:你不得不對 App 模塊進行修改加上一行 this.share = options.share,這明顯不是咱們所指望的。
雖然 App 經過依賴注入的方式在必定程度上解耦了與其餘幾個模塊的依賴關係,可是還不夠完全,其中的 this.router 和 this.track 等屬性其實都仍是對「具體實現」的依賴,明顯違背了 IoC 思想的準則,那如何進一步抽象 App 模塊呢。
Talk is cheap, show you the code
class App {
static modules = []
constructor(options) {
this.options = options;
this.init();
}
init() {
window.addEventListener('DOMContentLoaded', () => {
this.initModules();
this.options.onReady(this);
});
}
static use(module) {
Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);
}
initModules() {
App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));
}
}
通過改造後 App 內已經沒有「具體實現」了,看不到任何業務代碼了,那麼如何使用 App 來管理咱們的依賴呢:
// modules/Router.js
import Router from 'path/to/Router';
export default {
init(app) {
app.router = new Router(app.options.router);
app.router.to('home');
}
};
// modules/Track.js
import Track from 'path/to/Track';
export default {
init(app) {
app.track = new Track(app.options.track);
app.track.tracking();
}
};
// index.js
import App from 'path/to/App';
import Router from './modules/Router';
import Track from './modules/Track';
App.use([Router, Track]);
ew App({
router: {
mode: 'history',
},
track: {
// ...
},
onReady(app) {
// app.options ...
},
});
能夠發現 App 模塊在使用上也很是的方便,經過 App.use() 方法來「注入」依賴,在 ./modules/some-module.js 中按照必定的「約定」去初始化相關配置,好比此時須要新增一個 Share 模塊的話,無需到 App 內部去修改內容:
// modules/Share.js
import Share from 'path/to/Share';
export default {
init(app) {
app.share = new Share();
app.setShare = data => app.share.setShare(data);
}
};
// index.js
App.use(Share);
ew App({
// ...
onReady(app) {
app.setShare({
title: 'Hello IoC.',
description: 'description here...',
// some other data here...
});
}
});
直接在 App 外部去 use 這個 Share 模塊便可,對模塊的注入和配置極爲方便。
那麼在 App 內部到底作了哪些工做呢,首先從 App.use 方法提及:
class App {
static modules = []
static use(module) {
Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);
}
}
能夠很清楚的發現,App.use 作了一件很是簡單的事情,就是把依賴保存在了 App.modules 屬性中,等待後續初始化模塊的時候被調用。
接下來咱們看一下模塊初始化方法 this.initModules() 具體作了什麼事情:
class App {
initModules() {
App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));
}
}
能夠發現該方法一樣作了一件很是簡單的事情,就是遍歷 App.modules 中全部的模塊,判斷模塊是否包含 init 屬性且該屬性必須是一個函數,若是判斷經過的話,該方法就會去執行模塊的 init 方法並把 App 的實例 this 傳入其中,以便在模塊中引用它。
從這個方法中能夠看出,要實現一個能夠被 App.use() 的模塊,就必須知足兩個「約定」:
這其實就是 IoC 思想中對「面向接口編程 而不要面向實現編程」這一準則的很好的體現。App 不關心模塊具體實現了什麼,只要知足對 接口 init 的「約定」就能夠了。
此時回去看 Router 的模塊的實現就能夠很容易理解爲何要怎麼寫了:
// modules/Router.js
import Router from 'path/to/Router';
export default {
init(app) {
app.router = new Router(app.options.router);
app.router.to('home');
}
};最後總結:App 模塊此時應該稱之爲「容器」比較合適了,跟業務已經沒有任何關係了,它僅僅只是提供了一些方法來輔助管理注入的依賴和控制模塊如何執行。控制反轉(Inversion of Control)是一種「思想」,依賴注入(Dependency Injection)則是這一思想的一種具體「實現方式」,而這裏的 App 則是輔助依賴管理的一個「容器」。