前端理解依賴注入(控制反轉)

58

前端的技術的極速發展,對前端同學來說也是一個不小的挑戰,有各種各樣的東西需要學,在開發過程中經常會被後端同學嘲諷,對于前端來講根本就不存在類的概念,很多時候需要把大量的業務代碼堆積在頁面或者組件中,使組件和頁面變得特别的臃腫,一旦業務邏輯複雜的情況下,及時組件化做的很好,仍然避免不了難以維護。

之所以會被後端同學嘲諷,一基礎掌握不紮實,對前端理解不到位,二缺乏面向對象思想,三對業務與基礎傻傻分不清楚。ECMAScript 2015Type Script的推出,提出了一個很重要的概念就是class(類)的概念。在沒有class之前為了前端能夠有類的概念,一直都是使用構造函數模拟類的概念,通過原型完成繼承。

雖然前端提出了很多概念(模塊化,組件化...),個人覺得面向對象的應用是前端對于項目以及整體架構來講是一件利器,代碼結構好與壞與面向對象有一定的關系,但不是全部。不過我們可以借助計算機領域的一些優秀的編程理念來一定程度上解決這些問題,接下來簡單的說下依賴注入(控制反轉)。

什麼是依賴注入

依賴注入一般指控制反轉,是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入,還有一種方式叫依賴查找。通過控制反轉,對象在被創建的時候,由一個調控系統内所有對象的外界實體将其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。

從上面的描述中可以發現,依賴注入發生在2個或兩個以上類,比如現在有兩個類AB類,如果A是基礎類存在的話,B做為業務類存在,B則會依賴于A,上面有一句話很重要由一個調控系統内所有對象的外界實體将其所依賴的對象的引用傳遞給它,個人理解,在B類中使用A類的實例,而不是繼承A類。

對面向對象了解的同學應該了解,面向對象7大原則:

  1. 單一職責
  2. 開閉原則
  3. 裡氏替換
  4. 依賴倒置
  5. 接口隔離
  6. 迪米特法則
  7. 組合聚合複用原則

詳細解釋參照:面向對象之七大基本原則(javaScript)

然而使用依賴注入的事為了降低代碼的耦合程度,提高代碼的可拓展性。以上都是一些面向對象的思想,我們參考一下以上最重要的幾個原則,層模塊不應該依賴低層模塊。兩個都應該依賴抽象,抽象不應該依賴具體實現,具體實現應該依賴抽象。

// 球隊信息不依賴具體實現
// 面向接口即面向抽象編程
class Fruit {
    constructor(name) {
        this.name = name
    }
}
class Tropical {
    // 此處的參數,是teamInfo的一個實例,不直接依賴具體的實例
    // 面向抽象
    constructor(fruit) {
        this.fruit = fruit;
    }
    info() {
        console.log(this.fruit.name)
    }
}
// 将依賴關系放到此處來管理,控制權也放到此處
// Tropical和Fruit之間不再有直接依賴
// 原本直接掌握Fruit控制權的Tropical不再直接依賴
// 将依賴控制,落在此處(第三方模塊專門管理)即為控制反轉
var ym = new Tropical(new Fruit('香蕉'))
ym.info()
var kobe = new Tropical(new Fruit('菠蘿'))
kobe.info()

依賴注入的作用

初始化被依賴的模塊

如果不通過依賴注入模式來初始化被依賴的模塊,那麼就要依賴模塊自己去初始化了
那麼問題來了:依賴模塊就耦合了被依賴模塊的初始化信息了

注入到依賴模塊中

被依賴模塊已經被其他管理器初始化了,那麼依賴模塊要怎麼獲取這個模塊呢?

有兩種方式:

  • 自己去問
  • 别人主動給你

沒用依賴注入模式的話是1,用了之後就是2

想想,你需要某個東西的時候,你去找别人要,你需要提供别人什麼信息?最簡單的就是那個東西叫什麼,即你需要提供一個名稱。所以,方式1的問題是:依賴模塊耦合了被依賴模塊的名稱還有那個别人而方式2解決了這個問題,讓依賴模塊隻依賴需要的模塊的接口。

依賴注入的優點

依賴注入降低了依賴和被依賴類型間的耦合,在修改被依賴的類型實現時,不需要修改依賴類型的實現,同時,對于依賴類型的測試。依賴注入方式,可以将代碼耦合性降到最低,而且各個模塊拓展不會互相影響,

  1. 實現數據訪問層,也就是前端你的數據請求層
  2. 模塊與接口重構,依賴注入背後的一個核心思想是單一功能原則,這意味着這些對象在系統的任何地方都可以重用。
  3. 随時增加單元測試,把功能封裝到整個對象裡面會導緻自動測試困難或者不可能。将模塊和接口與特定對象隔離,以這種方式重構可以執行更先進的單元測試。

Vue中使用

上面寫的例子也隻是對依賴注入見單的使用,在項目過程中往往就不是這麼簡單了,肯定不會向例子這麼簡單,而是很複雜很龐大的一個項目。項目中分為各種各樣的模塊,這種情況又改如何處理?在JavaScript中常見的就是依賴注入。從名字上理解,所謂依賴注入,即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動态的将某種依賴關系注入到組件之中。

前端項目中并不像後端一樣,各種各樣的類,雖然前端可以寫class,若是React項目的話,還會好很多,由于其框架使用,全部是基于class但是在vue項目中,又應該怎麼具體體現呢?頁面中的業務也是可以作為類存在最終注入到Vue頁面中,最終完成業務邏輯。

o_7A56115D-CE2F-43e4-A824-35975D35502F.png

通過依賴注入到底想要達到到什麼效果呢,依賴注入最終想要達成的效果則是,頁面的表現與業務相脫離,從本質上來說,頁面的展現形式與業務邏輯其實沒有根本聯系的。若使用這種依賴注入的形式,則可以輕松的把業務和頁面表現分離開,頁面更加的專注于展示,而所注入的東西則更加的專注于業務的處理。項目也則會變得容易維護。

index.vue

<template>
  <div>
    <el-button @click="getList"
              :loadding="loadding">獲取表格數據</el-button>
    <ul>
      <li v-for="(item,index) of list"
          :key="index">{{item}}</li>
    </ul>
  </div>
</template>
<script>
import operation from "@/business/index/Operation.js";
export default {
  data() {
    return {
      list: [],
      query:{},
      loadding:false
    }
  },
  methods:{
    async getList(){
      let {query} = this;
      this.loadding = true;
      try{
        this.list = await operation.getData.call(this,query);
      }catch(error){
        console.log(error)
      }
      this.loadding =false;
    }
  }
}
</script>
<style>
@import "@/style/index.vue";
</style>

operations.js

import request from "@/api/errorList/index.js";
class Operation {
    async getData(query){
        //  this 指向Vue實例
        try {
            let res = await request.getErrorList(query);
            let {list} = res;
            //  這裡可以對數據進行二次處理
            //  寫一些其他業務
            Promise.resolve(list);
        }catch(error){
            Promise.reject(error);
        }
    }
};
export default new Operation();

上面也是在項目中的一個簡單的應用,使頁面表現數據請求與數據處理,業務相脫離,讓項目變得更加容易維護。

控制反轉這裡控制權從使用者本身轉移到第三方容器上,而非是轉移到被調用者上,這裡需要明确不要疑惑。控制反轉是一種思想,依賴注入是一種設計模式。

依賴注入最終想要達到的目的,首先得為模塊依賴提供抽象的接口,下來應該能夠注冊依賴關系
在注冊這個依賴關系後有地方存儲它,存儲後,我們應該把被依賴的模塊注入依賴模塊中,注入應該保持被傳遞函數的作用域,被傳遞的函數應該能夠接受自定義參數,而不僅僅是依賴描述。

總結

JavaScript中依賴注入的概念不像Java中被經常提到,主要原因是在js中很容易就實現了這種動态依賴。其實我們大部分人都用過依賴注入,隻是我們沒有意識到。即使你不知道這個術語,你可能在你的代碼裡用到它百萬次了。希望這篇文章能加深你對它的了解。

需要注意的是,依賴注入隻是控制反轉的一種實現方式,正确的依賴管理是每個開發周期中的關鍵過程。JavaScript 生态提供了不同的工具,作為開發者的我們應該挑選最适合自己的工具。

你可能感興趣的

14 條評論
Meathill · 9月8日

博主對“控制反轉”的理解是錯誤的。所謂“反轉”,是指類的依賴,不是在寫作代碼時,由開發者指定;而是在運行過程中,由運行時框架決定。所以你把它放在構造函數裡傳進去并沒有解構 A 對 B 的依賴,因為 B 需要在A 用到之前被實例化,實際上仍然是開發者在控制依賴。正确的做法應該是:

  1. 有一個框架管理所有注入項,具體到例子裡就是 fruit 實例
  2. 使用注入項的類需要聲明自己的需求,并提供初始化鈎子
  3. 使用時,框架負責構建注入項,創建使用者,并注入注入項

用僞代碼表示,大概是這樣的:

// framework.js
const fruitMap = new Map();
export function inject(instance) {
  // do inject
  instance.afterInjected();
}
export function map(key, value) {

}

// tropical.js
import framework from './framework';

export default class Tropical() {
  construct() {
    this.inject = ['fruit'];
    framework.inject(this);
  }

  afterInjected() {

  }
}

// index.js
import framework from './framework';

if ( // 某種邏輯) {
  framework.map('fruit', new Fruit('香蕉'));
} else ( // 另外一種邏輯) {
  framework.map('fruit', new Fruit('西瓜'));
}
const tropical = new Tropical();

以前有個非常經典的 AS 框架叫做 Robotlegs,可以好好看看它的 API 作為參考。

+4 回複

0

好的,感謝指導

Aaron 作者 · 9月8日
0

這個是對的,編寫代碼的時候隻是聲明了依賴的類型,執行的時候傳入真正的依賴實例

我了個水 · 9月9日
0

大佬,我研究了一下,你寫的東西,是不是把每個依賴對象,都轉換成一個實例對象,每個實例對其自身的對象進行對象自管理呢?

Aaron 作者 · 9月10日
standbill · 9月12日

依賴注入,和控制反轉是一種思想,
指類不指定太具體行為,或者說是隻指定普遍,廣泛行為。
其他具體實現由傳入構造函數的實例實現。

達到不使用繼承,來創造出不同表現的實例。

在前端,使用場景也挺多,vue 裡面slot ,provide inject .等,都是。

但是解決類似問題場景,也有很暴力的辦法,比如直接原型上加方法,相當于是在,修改基類。

回複

0

@standbill 當然,組件接收 bind this 的函數,内部再調用函數,也是。

standbill · 9月12日
yahiko · 9月18日

感覺沒聽懂啊,列舉的demo也沒有講清楚

回複

0
  • -!無能為力
Aaron 作者 · 9月18日
蘋果小蘿蔔 · 9月27日

有關IOC的概念和運用可以看下這篇文章:

https://juejin.im/post/5c2c47...

回複

Sweets82312 · 6 天前

看angularjs就可以了

回複

載入中...