前端中的 IoC 理念

2021-12-25 前端大全

(給前端大全加星標,提升前端技能)

作者:大板慄

https://zhuanlan.zhihu.com/p/53832991

背景

前端應用在不斷壯大的過程中,內部模塊間的依賴可能也會隨之越來越複雜,模塊間的 低復用性 導致應用 難以維護,不過我們可以藉助計算機領域的一些優秀的編程理念來一定程度上解決這些問題,接下來要講述的 IoC 就是其中之一。

什麼是 IoC

IoC 的全稱叫做 Inversion of Control,可翻譯為為「控制反轉」或「依賴倒置」,它主要包含了三個準則:

1、高層次的模塊不應該依賴於低層次的模塊,它們都應該依賴於抽象

2、抽象不應該依賴於具體實現,具體實現應該依賴於抽象

3、面向接口編程 而不要面向實現編程

概念總是抽象的,所以下面將以一個例子來解釋上述的概念:

假設需要構建一款應用叫 App,它包含一個路由模塊 Router 和一個頁面監控模塊 Track,一開始可能會這麼實現:

// app.js

import Router from './modules/Router';

import Track from './modules/Track';

class App {

    constructor(options) {

        this.options = options;

        this.router = new Router();

        this.track = new 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';

new App({

    onReady() {

        // do something here...

    },

});

嗯,看起來沒什麼問題,但是實際應用中需求是非常多變的,可能需要給路由新增功能(比如實現 history 模式)或者更新配置(啟用 history, new Router({ mode: 'history' }))。這就不得不在 App 內部去修改這兩個模塊,這是一個 INNER BREAKING 的操作,而對於之前測試通過了的 App 來說,也必須重新測試。

很明顯,這不是一個好的應用結構,高層次的模塊 App 依賴了兩個低層次的模塊 Router 和 Track,對低層次模塊的修改都會影響高層次的模塊 App。那麼如何解決這個問題呢,解決方案就是接下來要講述的 依賴注入(Dependency Injection)

依賴注入

所謂的依賴注入,簡單來說就是把高層模塊所依賴的模塊通過傳參的方式把依賴「注入」到模塊內部,上面的代碼可以通過依賴注入的方式改造成如下方式:

// 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';

new 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]);

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

new 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() 的模塊,就必須滿足兩個「約定」:

模塊必須包含 init 屬性

init 必須是一個函數

這其實就是 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 則是輔助依賴管理的一個「容器」。

覺得本文對你有幫助?請分享給更多人

關注「前端大全」加星標,提升前端技能

喜歡就點一下「好看」唄~

相關焦點

  • 問答丨F-35戰機從IOC階段提升到FOC階段,戰鬥力提升高嗎?
    大黃:大伊萬,我是剛入圈的,問一下ioc與foc有什麼區別?戰鬥力提升很大嗎?
  • 5G射頻前端模組的前世與今生
    最近十幾年中,射頻前端方案快速演進。「模組化」是射頻前端演進的重要方向。 射頻前端的「模組化」究竟是什麼, 它是怎麼來的,又有什麼挑戰? 帶著以上問題,本文對射頻前端模組的發展過程做一個梳理,對射頻前端產品模組化進程中的挑戰和未來可能的演進做一個討論。射頻前端是指天線後,收發機之前的部分。
  • 射頻前端模組
    該模塊集成了整個射頻前端所需要的發射頻段。包括帶偏差控制器的寬帶PA、Tx諧波濾波器、天線開關和MIPI控制器。Smarter Micro慧智微WiFi射頻前端 S3217高度集成5GHz前端模塊(FEM),包含5GHz的單杆、雙擲(SPDT)傳輸/接收(t/r)開關,和帶支路的5 GHz低噪音放大器(LNA),
  • 前端離線化探索
    比如,用戶點擊按鈕,前端更新數據狀態為成功,請求到達後臺,伺服器響應,更新前端數據。在漸進式web應用pwa中,sw為Network independent特性提供了最核心的支持。藉助CacheStorage,我們可以在 sw 安裝激活的生命周期中,按需填充緩存資源,然後在fetch 事件中,攔截 http 請求,將緩存資源或者自定義消息返回給頁面。service-worker 實現了真正的可用性及安全性。
  • 數據採集前端DLHY-4E說明書
    ,該產品可以在不加中間環節的情況下直接實現和DCS系統的通訊,目前已在國內各容量機組中有很多的應用,同時實現了和多家DCS系統的直接通訊。                    圖4  DLHY-4E端子配置圖中每個通道的H、L、G依次表示信號輸入的正極、負極1和負極2。
  • 前端很難?來學學超級簡單的布局方式:Flex!
    我一個寫後端的程式設計師,需要去學習前端的知識嗎?渣哥的回答是:當然需要,並且非常需要!首先從就業上來說,會寫後端是你的資本,你會別人也會,但是會寫前端,是你的亮點,你會,別人不會,這就是你的加分項。其次從實際項目開發中來說,無論是你想寫一個自己的練手項目,或者有興趣自己鼓搗點什麼系統玩一玩,再者說如果遇到實習或者就業的公司沒有專門的前端開發人員,需要你自己寫頁面的話,這些時候,如果你只會後端,業務邏輯設計的再完美,沒有前端的呈現,終究還是一場空,就像你是一個非常優秀的廚師,你做好了一道非常好吃的菜,但是你沒有盤子,端不出去給客人品嘗,那這道美味佳餚只能你獨自欣賞了。
  • 澤塔奧特曼中的無人島,為什麼不能變成怪獸島?理念不同
    想要實現這個目標,和人類的理念有很大的關係,並且還需要有條件,比如讓怪獸生存的環境。並不是所有的奧特曼系列中,人類都會給怪獸找到合適的居所,所以大部分只能以消滅怪獸為目的。但是,這個條件因素,在澤塔奧特曼中,卻早已經具備。
  • 前端切圖神器Avocode
    ,所以切圖是作為一個前端的基本技能。殺敵要有趁手的兵器,而前端一般都是用photoshop,但是這個兵器非常笨重,可能當你笨拙的揮動的它時,敵人已經揮刀到你的頸部了。畢竟不是所有人都是楊過。所以我們應該選擇輕便鋒利的武器。那麼現在我來介紹一下一個很好用的前端切圖神器avocode。
  • 前端切圖神器avocode
    01安裝avocode前端的基礎工作就是把設計師的設計稿還原成前端頁面,所以切圖是作為一個前端的基本技能。
  • RF射頻前端組成及原理
    射頻前端是指在通訊系統中,天線和中頻(或基帶)電路之間的部分。
  • 分享一個前後端分離方案-前端angularjs+requirejs+dhtmlx 後端asp.net webapi
    前端項目dhtmlx_web:      開發工具 Sublime Text      前端框架angularjs         模塊加載requirejs         前端UI dhtmlx + bt3      包管理 bower         構建工具 gruntjs         服務架設 http_server.js
  • 前端工程師必備技能匯總
    項目起源還記得@jayli 的這幅前端知識結構圖麼
  • 前端安全之 XSS 攻擊
    前端在顯示服務端數據時候,不僅是標籤內容需要過濾、轉義,就連屬性值也都可能需要。2. 後端接收請求時,驗證請求是否為攻擊請求,攻擊則屏蔽。PS:因為大部分文章是保存整個HTML內容的,前端顯示時候也不做過濾,就極可能出現這種情況。結論:後端儘可能對提交數據做過濾,在場景需求而不過濾的情況下,前端就需要做些處理了。開發安全措施:1. 首要是服務端要進行過濾,因為前端的校驗可以被繞過。
  • 前端安全之XSS攻擊
    前端在顯示服務端數據時候,不僅是標籤內容需要過濾、轉義,就連屬性值也都可能需要。2. 後端接收請求時,驗證請求是否為攻擊請求,攻擊則屏蔽。結論:後端儘可能對提交數據做過濾,在場景需求而不過濾的情況下,前端就需要做些處理了。開發安全措施:1. 首要是服務端要進行過濾,因為前端的校驗可以被繞過。2.
  • 可信前端之路-代碼保護
    從這個角度講,我們把其當做一個美好的願景,我們希望能夠構造一個web系統中的TPM,可以把惡意行為控制在一定的概率之內,從而實現一個相對可信的web系統。0x01 可信前端在可信系統中,TPM的一個重要作用就是鑑別消息來源的真實性,保障終端的可信。在web系統中,我們的消息來源就是用戶。
  • 可視化:前端人的未來
    作為前端領域中一個幾乎不用寫網頁的特殊分支,可視化利用計算機的圖形學和圖像處理技術,將數據轉換成圖形或圖像,在屏幕上顯示出來,並進行交互處理。 它可以實現很多傳統 Web 網頁無法實現的效果,應用領域也非常廣泛,除了前面說過的疫情地圖以外,還有淘寶「雙 11」的可視化大屏、平臺的年度帳單、企業級應用中的態勢感知和指揮調度大屏,甚至是國家大力推廣的智慧城市、智慧生活等等。
  • 幾個比較受歡迎的前端框架
    如今的 CSS 前端框架的發展非常迅猛,但是真正好的框架不多。在這篇文章中,我將對我認為當今最好的 5 個框架進行比較。每個框架都有它自己的強項和弱項,以及適合的領域,你需要根據這些特點來進行選擇。例如,如果你的項目很簡單,那就沒有必要選擇複雜的框架。此外,還有一些選項是模塊化,這樣你就可以根據需要選擇所需的組件,或者來自不同框架混合的組件。
  • 前端工程師的一大神器——puppeteer
    Frame:頁面中的框架,在每個時間點,頁面通過page.mainFrame()和frame.childFrames()方法暴露當前框架的細節。對於該框架中至少有一個執行上下文ExecutionCOntext:表示一個JavaScript的執行上下文。Worker:具有單個執行上下文,便於與 WebWorkers 交互。
  • 前端專題之npm包
    由於頂級node_modules已經有了prop-types@15.5.0,所以react內部所依賴的prop-types@15.6.2會安裝在react內部的node_modules中。npm 出現之前:前端依賴項是保存到存儲庫中並手動下載的📁2012:npm 的使用量急劇增加——主要是由於 Browserifys 瀏覽器的支持🎉2012:npm 有了一個競爭對手 bower,它完全支持瀏覽器💻2012-2016:前端項目的依賴項數量成倍增加🤯2012-2016:構建和安裝前端應用變得越來越慢🐢2012-2016:大量(重複的)依賴項存儲在神奇的 node_modules
  • 【理論研究】新古典現實主義中的利益、理念與國家行為研究丨國政學人
    將理念概念化為外化的信念能夠規避這些問題。理念的半論述概念(semi-discursive conception)在本體論上「足以夠用」。理念可以由具體的人(基於信念)創造,(在交流中)散播,(作為變量)嵌入因果關係的排序中,改變(例如,被遺忘)。理念作為一種外化的信念在概念上有別於文化、意識形態等這類更集中、更連貫、更持久的現象。