忙里偷闲,打开平时关注的前端相关的网站,浏览最近最新的前端动态。佼佼者,平凡的我做不到,但还是要争取不做落后者。

前端中的IoC理念,看到这个标题就被吸引了。IoC 理念,不认识呢,点击去一看,果然没让我失望,原文结合案例把概念诠释的很清楚。原来 Ioc控制反转依赖倒置

控制反转依赖倒置依赖注入 这三个名词,我倒是很耳熟了,毕竟在大学学 java web 课程的时候接触过,记得当时还很认真的学了并做了笔记。时间真是遗忘的罪魁祸首,现在就只记得名词,而全然忘了概念。

什么是 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 内部去修改这两个模块,而对于之前测试通过了的 App 来说,也必须重新测试。

很明显,这不是一个好的应用结构,高层次的模块 App 依赖了两个低层次的模块 Router 和 Track,对低层次模块的修改都会影响高层次的模块 App。那么如何解决这个问题呢,解决方案就是接下来要讲述的 依赖注入(Dependency Injection)。

什么是依赖注入?

所谓的依赖注入,简单来说就是把高层模块所依赖的模块通过传参的方式把依赖「注入」到模块内部,上面的代码可以通过依赖注入的方式最终改造成如下方式:

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 中按照一定的「约定」去初始化相关配置即可。

要实现一个可以被 App.use() 的模块,就必须满足两个约定

  1. 模块必须包含 init 属性
  2. init 必须是一个函数

这其实就是 IoC 思想中对面向接口编程 而不要面向实现编程这一准则的很好的体现。App 不关心模块具体实现了什么,只要满足对 接口init约定就可以了。

在理解了本文的后,再回首看 koa 中的 app.use(),理解起来就没那么难了。

koa 中的 app.use()

koa 的源码文件中关于 constructor 的代码如下:

  constructor() {
super(); this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}

koa 的源码文件中关于 use 的代码如下:

  use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}

可以看到,可上面的案例很相似,只不过再没有init方法,而是直接调用方法。

而实际使用的时候,是这样的:

const Koa = require('koa')
const app = new Koa() const m1 = require('./middleware/koa-m1') // 模拟的中间件
app.use(m1())
// 中间件
function m1 (ctx) {
global.console.log('m1', ctx.path)
} module.exports = function () {
return async function (ctx, next) {
global.console.log('m1 start')
m1(ctx)
await next()
global.console.log('m1 end')
}
}

总结:IoC 的理念,很值得编程借鉴。刚接触前端时,只知道html、css、JavaScript。而现在发现难的地方,很多都是借鉴了后端的编程思想。比如设计模式,SSR(其实本质应该就是JSP)。IoC 的理念,我想最初的实现应该也是后端。

最后深入了解:

从前端中的IOC理念理解koa中的app.use()的更多相关文章

  1. 前端中的 IoC 理念

    背景 前端应用在不断壮大的过程中,内部模块间的依赖可能也会随之越来越复杂,模块间的 低复用性 导致应用 难以维护,不过我们可以借助计算机领域的一些优秀的编程理念来一定程度上解决这些问题,接下来要讲述的 ...

  2. 深入理解koa中的co源码

    阅读目录 一:理解Generator 二:理解js函数柯里化 三:理解Thunk函数 四:理解CO源码 回到顶部 一:理解Generator 在看co源码之前,我们先来理解下Generator函数.G ...

  3. 图说js中的this——深入理解javascript中this指针

    没搞错吧!js写了那么多年,this还是会搞错!没搞错,javascript就是回搞错! ………… 文章来源自——周陆军的个人网站:http://zhoulujun.cn/zhoulujun/html ...

  4. JQuery选择器大全 前端面试送命题:面试题篇 对IOC和DI的通俗理解 c#中关于协变性和逆变性(又叫抗变)帮助理解

    JQuery选择器大全   jQuery 的选择器可谓之强大无比,这里简单地总结一下常用的元素查找方法 $("#myELement")    选择id值等于myElement的元素 ...

  5. 前端 IoC 理念入门

    背景 近几年,前端应用(WebApp)正朝着大规模方向发展,在这个过程中我们会对项目拆解成多个模块/组件来组合使用,以此提高我们代码的复用性,最终提高研发效率. 在编写一个复杂组件的时候,总会依赖其他 ...

  6. 浅析对spring中IOC的理解

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  7. Spring中IOC的理解

    Spring中IOC的理解 1.什么是IOC? (1)控制反转.把对象创建和对象间的调用过程交给Spring进行管理. (2)使用IOC的目的:为了耦合度降低. 2.IOC底层原理? (1)xml解析 ...

  8. 理解Spring中的IoC和DI

    什么是IoC和DI IoC(Inversion of Control 控制反转):是一种面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度.其基本思想是:借助于"第三方" ...

  9. 前端知识体系:JavaScript基础-原型和原型链-理解 es6 中class构造以及继承的底层实现原理

    理解 es6 中class构造以及继承的底层实现原理 原文链接:https://blog.csdn.net/qq_34149805/article/details/86105123 1.ES6 cla ...

随机推荐

  1. vm虚拟机网关配置

    今天因为环境搭建,在配置完,外部始终无法访问虚拟机,一时想不出道理,后来经过端口测试,发现应该是网关配置问题,留个备注,防止以后出现同样的问题.

  2. mycat读写分离性能测试

    1.    MySQL主从配置 我们的方案设计基于Mysql的主从数据复制功能,以下是基于mysql5.5以上版本最新的主从配置. 开启mysql主从数据复制,主要在mysql的my.ini文件中设置 ...

  3. C#下载Url文件到本地

    protected void Page_Load(object sender, EventArgs e) { string filePath = Request.Params["FilePa ...

  4. this()基础用法

    this()表示调用构造方法,此种调用只能用在构造方法中,即构造方法中调用构造方法this(实参). 1.this().this(实参)必须方法构造方法的第一行 2.在有参数构造方法中调用无参数构造方 ...

  5. PHP去掉html中的空行、空白函数

    function DeleteHtml($str){ $str = trim($str); $str = ereg_replace("\t","",$str); ...

  6. linux下安装srilm

    1.安装相关依赖库 a.c/c++ compiler:编译器gcc 3.4.3及以上版本   b.GNU make:构建和管理工程的工具,解释Makefile里的指令,描述了整个工程所有文件的编译顺序 ...

  7. 通过HashSet达到对象集去重的实现(jdk1.8)

    通过HashSet达到对象集去重的实现(jdk1.8) public class Contract { private String contractId; private String contra ...

  8. 阿里云远程连接CentOS

    1.购买一个CentOS的ECS服务器: 2.修改安全组,开放SSH/22的端口号: 这里是22/22为SSH连接的端口号:3389为远程桌面的默认端口号 3.利用xshell或者SecureCRT连 ...

  9. 将n个东西分成n1,n2,n3,n4,....nr 共 r组分给r个人有多少种分法。

    (n!/(n1! *n2! *n3!..nr!) )   * r!/( 同数量组A的数量! 同数量组B的数量!....) 比方20个东西分成2,2,,2,2   3,3,3,3 8组分给8个人有多少种 ...

  10. python3 django1.11 安装xadmin 的方法,亲测可用

    首先需要Pip安装如下的包ip install django-crispy-forms pip install django-import-export pip install django-reve ...