zoukankan      html  css  js  c++  java
  • 从前端中的IOC理念理解koa中的app.use()

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

    前端中的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 的理念,我想最初的实现应该也是后端。

    最后深入了解:

  • 相关阅读:
    Ajax中onreadystatechange函数不执行,是因为放在open()后
    js调用ajax案例2,使用ok
    js调用ajax案例
    通过设置ie的通过跨域访问数据源,来访问本地服务
    Net 4.5 WebSocket 在 Windows 7, Windows 8 and Server 2012上的比较以及问题
    Net 4.5 WebSocket 在 Windows 7, Windows 8 and Server 2012上的比较
    windows 系统纯净版官网下载地址
    iOS:给Git仓库上传代码时,超过100M会被拒绝(例如github和oschina)
    iOS:Xcode8以下真机测试iOS10.0和iOS10.1配置包
    iOS:高德地图的使用
  • 原文地址:https://www.cnblogs.com/EnSnail/p/10305363.html
Copyright © 2011-2022 走看看