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

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

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

什么是 IoC ?

IoC 的全称是Inversion of Control,翻译为 控制反转依赖倒置,主要包含了三个准则:

  1. 高层次的模块不应该依赖于底层次的模块,它们都应该依赖于抽象
  2. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象
  3. 面向接口编程,而不要面向实现编程

举例

假设需要构建一款应用叫 App,它包含一个路由模块 Router 和一个页面监控模块 Track,一开始可能会这么实现:

  1. // app.js
  2. import Router from './modules/Router';
  3. import Track from './modules/Track';
  4. class App {
  5. constructor(options) {
  6. this.options = options;
  7. this.router = new Router();
  8. this.track = new Track();
  9. this.init();
  10. }
  11. init() {
  12. window.addEventListener('DOMContentLoaded', () => {
  13. this.router.to('home');
  14. this.track.tracking();
  15. this.options.onReady();
  16. });
  17. }
  18. }
  19. // index.js
  20. import App from 'path/to/App';
  21. new App({
  22. onReady() {
  23. // do something here...
  24. },
  25. });

看起来没什么问题,但是实际应用中需求是非常多变的,可能需要给路由新增功能(比如实现 history 模式)或者更新配置(启用 history, new Router({ mode: 'history' }))。这就不得不在 App 内部去修改这两个模块,而对于之前测试通过了的 App 来说,也必须重新测试。

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

什么是依赖注入?

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

  1. class App {
  2. static modules = []
  3. constructor(options) {
  4. this.options = options;
  5. this.init();
  6. }
  7. init() {
  8. window.addEventListener('DOMContentLoaded', () => {
  9. this.initModules();
  10. this.options.onReady(this);
  11. });
  12. }
  13. static use(module) {
  14. Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);
  15. }
  16. initModules() {
  17. App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));
  18. }
  19. }

经过改造后 App 内已经没有「具体实现」了,看不到任何业务代码了,那么如何使用 App 来管理我们的依赖呢:

  1. // modules/Router.js
  2. import Router from 'path/to/Router';
  3. export default {
  4. init(app) {
  5. app.router = new Router(app.options.router);
  6. app.router.to('home');
  7. }
  8. };
  9. // modules/Track.js
  10. import Track from 'path/to/Track';
  11. export default {
  12. init(app) {
  13. app.track = new Track(app.options.track);
  14. app.track.tracking();
  15. }
  16. };
  17. // index.js
  18. import App from 'path/to/App';
  19. import Router from './modules/Router';
  20. import Track from './modules/Track';
  21. App.use([Router, Track]);
  22. new App({
  23. router: {
  24. mode: 'history',
  25. },
  26. track: {
  27. // ...
  28. },
  29. onReady(app) {
  30. // app.options ...
  31. },
  32. });

可以发现 App 模块在使用上也非常的方便,通过 App.use() 方法来「注入」依赖,在 ./modules/some-module.js 中按照一定的「约定」去初始化相关配置即可。

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

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

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

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

koa 中的 app.use()

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

  1. constructor() {
  2. super();
  3. this.proxy = false;
  4. this.middleware = [];
  5. this.subdomainOffset = 2;
  6. this.env = process.env.NODE_ENV || 'development';
  7. this.context = Object.create(context);
  8. this.request = Object.create(request);
  9. this.response = Object.create(response);
  10. if (util.inspect.custom) {
  11. this[util.inspect.custom] = this.inspect;
  12. }
  13. }

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

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

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

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

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const m1 = require('./middleware/koa-m1') // 模拟的中间件
  4. app.use(m1())
  1. // 中间件
  2. function m1 (ctx) {
  3. global.console.log('m1', ctx.path)
  4. }
  5. module.exports = function () {
  6. return async function (ctx, next) {
  7. global.console.log('m1 start')
  8. m1(ctx)
  9. await next()
  10. global.console.log('m1 end')
  11. }
  12. }

总结: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. 《笨方法学Python》加分题16

    基础部分 # 载入 sys.argv 模块,以获取脚本运行参数. from sys import argv # 将 argv 解包,并将脚本名赋值给变量 script :将参数赋值给变量 filena ...

  2. JAVA实现等腰三角形

    class Triangle { public static void main(String[] args) { for(int a=0; b<5; a++)//这个代表只有四行 { for( ...

  3. 查看Chrome密码只需一段代码

    在Chrome浏览器的地址栏里输入“ chrome://chrome/settings/passwords ”,然后Chrome自动帮你保存的密码就会瞬间被曝露出来. 强调一下,只要不是在本机上输入以 ...

  4. cobbler自动装机服务简介与配置

    cobbler简介 Cobbler是一个Linux服务器安装的服务,可以通过网络启动(PXE)的方式来快速安装.重装物理服务器和虚拟机,同时还可以管理DHCP,DNS等. Cobbler可以使用命令行 ...

  5. 常见的hbase jar

  6. 非root用户安装cuda和cudnn

    1.根据自己的系统在官网下载cuda (选择runfile(local)) https://developer.nvidia.com/cuda-downloads 2.进入下载目录,并执行 sh cu ...

  7. NPOI颜色对照表

    颜色对照表:颜色 测试 Class名称 short        Test颜色 Black 8 Test颜色 Brown 60 Test颜色 Olive_Green 59 Test颜色 Dark_Gr ...

  8. powershell脚本找修改的文件

    $sourcedir="D:\workspace" $targetdir="E:\newf" $lastdate="2017-05-19" ...

  9. linux_基本命令使用(后续更新)

    安装文件上传下载快捷键 --> rz/sz yum -y install lrzsz 获取进程名.进程号以及用户ID netstat –nlpt 修改主机名(重启后永久生效)-->cent ...

  10. 解决在静态页面上使用动态参数,造成spider多次和重复抓取的问题

    我们在使用百度统计中的SEO建议检查网站时,总是发现“静态页参数”一项被扣了18分,扣分原因是“在静态页面上使用动态参数,会造成spider多次和重复抓取”.一般来说静态页面上使用少量的动态参数的话并 ...