前端领域的 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 方法必须是函数,并可以获取容器传来的参数。

总结

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

相关推荐
前端小趴菜05几秒前
React - 组件通信
前端·react.js·前端框架
Amy_cx20 分钟前
在表单输入框按回车页面刷新的问题
前端·elementui
dancing99934 分钟前
cocos3.X的oops框架oops-plugin-excel-to-json改进兼容多表单导出功能
前端·javascript·typescript·游戏程序
后海 0_o1 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
Scabbards_1 小时前
CPT304-2425-S2-Software Engineering II
前端
喵叔哟1 小时前
24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
微服务·架构·.net
小满zs1 小时前
Zustand 第二章(状态处理)
前端·react.js
程序猿小D1 小时前
第16节 Node.js 文件系统
linux·服务器·前端·node.js·编辑器·vim
萌萌哒草头将军1 小时前
🚀🚀🚀Prisma 发布无 Rust 引擎预览版,安装和使用更轻量;支持任何 ORM 连接引擎;支持自动备份...
前端·javascript·vue.js
java干货1 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构