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

总结

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

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐4 小时前
前端图像处理(一)
前端