前端领域的 IoC 理念

是什么?

Inversion of Control(IoC)是一种软件设计原则,它指的是控制权的转移。在传统的程序设计中,开发者通常会控制程序的执行流程和对象的创建与管理。而在IoC容器中,控制权被反转,由容器来管理和控制对象的生命周期、依赖注入等行为。IoC通过控制反转的方式改变了传统程序的控制流程,将对象的创建和管理交由容器来完成,从而使系统更易于扩展、维护和测试。

为什么?

前端应用不断壮大, 内部模块间的依赖越来越复杂, 模块间的低复用性导致应用难以维护。

准则:

高层次的模块不应该依赖于低层次的模块,应该依赖于抽象 抽象不应该依赖于具体的实现,具体应该依赖于抽象 面向接口编程、而不要面向实现编程

怎么做?

举一个🌰

在项目的入口文件,需要实例化一个 App 对象,这个对象是一个入口组件,并给 App 传入了生命周期的钩子函数。 在 App 的构造方法中实例化了 Router、 Track模块。并在页面加载后执行Router、 Track的方法和生命周期钩子。

js 复制代码
// 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 './app';
new App({
    onReady() {
        // do something here...
    },
});

这样写的问题是:

  1. App 作为高层次的模块,需要依赖具体的模块,即传入的参数 onReady 方法 和 Router、 Track 模块。
  2. 面向实现, 在监听里面需要依赖 router track 的实现。
  3. 如果要给 Router 增加模式,如要在 App 中和 ./modules/Router 分别添加代码。

我们可以先收缩依赖的入口,把 App 依赖的模块都通过参数注入。

js 复制代码
// app.js
class App {
    constructor(options) {
        this.options = options;
        this.router = options.router;
        this.track = options.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';
import Router from './modules/Router';
import Track from './modules/Track';

new App({
    router: new Router(),
    track: new Track(),
    onReady() {
        // do something here...
    },
});

下一步是要解决 App 对象对 Router、 Track模块实现的依赖。 这里可以把 App 对象当做一个容易,她只收集依赖的模块和执行模块指定的方法。

js 复制代码
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));
    }
}

依赖的模块,需要实现特定的方法,这里是 init 方法。

js 复制代码
// 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();
    }
};

入口文件负责依赖注入,和定义依赖模块所需的参数。

js 复制代码
// 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 中新增依赖,只需要定义模块,并在入口文件中注入。

js 复制代码
// modules/Share.js
import Share from 'path/to/Share';
export default {
    init(app) {
        app.share = new Share();
        app.setShare = data => app.share.setShare(data);
    }
};

// index.js
App.use(Share);
new App({
    // ...
    onReady(app) {
        app.setShare({
            title: 'Hello IoC.',
            description: 'description here...',
            // some other data here...
        });
    }
});

这样下来,App 对象就变成了面向对象而非面向实现的容器了。

  1. App.use 用来注册依赖。
  2. 依赖必须实现 init 方法。
  3. init 方法必须是函数,并可以获取容器传来的参数。

总结

高层模块作为容器,提供收集依赖的接口,并在内部实现对依赖管理的方法。

相关推荐
一斤代码1 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子1 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年1 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子2 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina2 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路3 小时前
React--Fiber 架构
前端·react.js·架构
伍哥的传说3 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409193 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app
我在北京coding3 小时前
element el-table渲染二维对象数组
前端·javascript·vue.js
布兰妮甜3 小时前
Vue+ElementUI聊天室开发指南
前端·javascript·vue.js·elementui