深圳幻海软件技术有限公司 欢迎您!

前端理解依赖注入(控制反转)

2023-02-27

前端的技术的极速发展,对前端同学来说也是一个不小的挑战,有各种各样的东西需要学,在开发过程中经常会被后端同学嘲讽,对于前端来讲根本就不存在类的概念,很多时候需要把大量的业务代码堆积在页面或者组件中,使组件和页面变得特别的臃肿,一旦业务逻辑复杂的情况下,及时组件化做的很好,仍然避免不了难以维护。之所以
前端的技术的极速发展,对前端同学来说也是一个不小的挑战,有各种各样的东西需要学,在开发过程中经常会被后端同学嘲讽,对于前端来讲根本就不存在类的概念,很多时候需要把大量的业务代码堆积在页面或者组件中,使组件和页面变得特别的臃肿,一旦业务逻辑复杂的情况下,及时组件化做的很好,仍然避免不了难以维护。

之所以会被后端同学嘲讽,一基础掌握不扎实,对前端理解不到位,二缺乏面向对象思想,三对业务与基础傻傻分不清楚。ECMAScript 2015与Type Script的推出,提出了一个很重要的概念就是class(类)的概念。在没有class之前为了前端能够有类的概念,一直都是使用构造函数模拟类的概念,通过原型完成继承。

虽然前端提出了很多概念(模块化,组件化...),个人觉得面向对象的应用是前端对于项目以及整体架构来讲是一件利器,代码结构好与坏与面向对象有一定的关系,但不是全部。不过我们可以借助计算机领域的一些优秀的编程理念来一定程度上解决这些问题,接下来简单的说下依赖注入(控制反转)。

什么是依赖注入

依赖注入一般指控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入,还有一种方式叫依赖查找。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

从上面的描述中可以发现,依赖注入发生在2个或两个以上类,比如现在有两个类A与B类,如果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.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

 

依赖注入的作用

初始化被依赖的模块

如果不通过依赖注入模式来初始化被依赖的模块,那么就要依赖模块自己去初始化了

那么问题来了:依赖模块就耦合了被依赖模块的初始化信息了

注入到依赖模块中

被依赖模块已经被其他管理器初始化了,那么依赖模块要怎么获取这个模块呢?

有两种方式:

  • 自己去问
  • 别人主动给你

没用依赖注入模式的话是1,用了之后就是2

想想,你需要某个东西的时候,你去找别人要,你需要提供别人什么信息?最简单的就是那个东西叫什么,即你需要提供一个名称。所以,方式1的问题是:依赖模块耦合了被依赖模块的名称还有那个别人而方式2解决了这个问题,让依赖模块只依赖需要的模块的接口。

依赖注入的优点

依赖注入降低了依赖和被依赖类型间的耦合,在修改被依赖的类型实现时,不需要修改依赖类型的实现,同时,对于依赖类型的测试。依赖注入方式,可以将代码耦合性降到最低,而且各个模块拓展不会互相影响,

  1. 实现数据访问层,也就是前端你的数据请求层
  2. 模块与接口重构,依赖注入背后的一个核心思想是单一功能原则,这意味着这些对象在系统的任何地方都可以重用。
  3. 随时增加单元测试,把功能封装到整个对象里面会导致自动测试困难或者不可能。将模块和接口与特定对象隔离,以这种方式重构可以执行更先进的单元测试。

Vue中使用

上面写的例子也只是对依赖注入见单的使用,在项目过程中往往就不是这么简单了,肯定不会向例子这么简单,而是很复杂很庞大的一个项目。项目中分为各种各样的模块,这种情况又改如何处理?在JavaScript中常见的就是依赖注入。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

前端项目中并不像后端一样,各种各样的类,虽然前端可以写class,若是React项目的话,还会好很多,由于其框架使用,全部是基于class但是在vue项目中,又应该怎么具体体现呢?页面中的业务也是可以作为类存在最终注入到Vue页面中,最终完成业务逻辑。

 

 

 

 

通过依赖注入到底想要达到到什么效果呢,依赖注入最终想要达成的效果则是,页面的表现与业务相脱离,从本质上来说,页面的展现形式与业务逻辑其实没有根本联系的。若使用这种依赖注入的形式,则可以轻松的把业务和页面表现分离开,页面更加的专注于展示,而所注入的东西则更加的专注于业务的处理。项目也则会变得容易维护。

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> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

 

 

 

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(); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

 

上面也是在项目中的一个简单的应用,使页面表现数据请求与数据处理,业务相脱离,让项目变得更加容易维护。

控制反转这里控制权从使用者本身转移到第三方容器上,而非是转移到被调用者上,这里需要明确不要疑惑。控制反转是一种思想,依赖注入是一种设计模式。

依赖注入最终想要达到的目的,首先得为模块依赖提供抽象的接口,下来应该能够注册依赖关系

在注册这个依赖关系后有地方存储它,存储后,我们应该把被依赖的模块注入依赖模块中,注入应该保持被传递函数的作用域,被传递的函数应该能够接受自定义参数,而不仅仅是依赖描述。

总结

在JavaScript中依赖注入的概念不像Java中被经常提到,主要原因是在js中很容易就实现了这种动态依赖。其实我们大部分人都用过依赖注入,只是我们没有意识到。即使你不知道这个术语,你可能在你的代码里用到它百万次了。希望这篇文章能加深你对它的了解。

需要注意的是,依赖注入只是控制反转的一种实现方式,正确的依赖管理是每个开发周期中的关键过程。JavaScript 生态提供了不同的工具,作为开发者的我们应该挑选最适合自己的工具。