实现发布订阅模式

订阅

在我们的生活中,订阅的现象随处可见,比如我们到书店里要买一本书,但目前并没有到货,我们到柜台前留下信息,店长在到货后变通过我们留下的信息来通知我们货到了,这便是订阅的过程。

发布订阅模式

1.你去买东西断货了, 老板让你留下联系方式, 到货了给你打电话, 这就是发布订阅模式
2.老板就是发布者, 你就是订阅者
3.订阅者将电话添加到了发布者的缓存列表中(电话簿)
4.当发布者到货后就会遍历缓存列表依次通知所有订阅者

实现

// 定义一个类作为发布者
class SyncHook {
    constructor(args){
        // 缓存列表
        this.tasks = [];
        // 定义属性保存将来会给订阅者传递多少个参数
        this.args = args;
    }
    // 用于订阅的方法
    tap(tag, task){
        this.tasks.push(task);
    }
    // 用于发布的方法
    call(...args){
        if(args.length < this.args.length){
            return new Error("参数个数不对");
        }
        // 剔除多余的参数
        args = args.slice(0, this.args.length);
        this.tasks.forEach(function (task) {
            task(...args);
        })
    }
}
module.exports = SyncHook;

接下来我们创建一个发布者:

// 1.创建发布者
let hook = new SyncHook(["name", "price"]);
// 2.订阅者像发布者订阅
hook.tap("zs", function (name, price) {
    console.log(name, price);
});
hook.tap("ls", function (name, price) {
    console.log(name, price);
});
hook.tap("ww", function (name, price) {
    console.log(name, price);
});

最后我们发布消息:

// 3.发布者发布消息
hook.call("豪车", 88888, 666);

实现原理很简单,就是创建一个发布者类,在其中创建一个列表用于保存订阅者的信息及默认传入参数的个数,接下来对齐遍历赋值即可。

Tapable

这是一个发布订阅模式的插件,它实现了我们上面的功能,并且还拥有众多在不同需求下的功能。

这些是它的配置:

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
 } = require("tapable");

这些便是它内置的发布者。

SyncHook同步串行钩子:
SyncHook不关心订阅函数(事件处理函数)的返回值,在收到消息(触发事件)之后,
会按照订阅的先后顺序执行所有的订阅函数(事件处理函数)

SyncBailHook同步串行钩子:
SyncBailHook关心订阅函数(事件处理函数)的返回值,在收到消息(触发事件)之后,
会按照订阅的先后顺序执行所有的事件处理函数

如果你查阅webpack底层源码, 你会发现webpack内部其实是由大量的插件构成的。Webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable. 所以说不懂 tapable 就看不懂 Webpack 源码。

并且在前端开发中发布订阅模式的使用频率非常高。
例如: webpack、Vue、React、Angular等各大框架都在用

我们选取三个来进行说明:

SyncHook
const { SyncHook } = require('tapable');

class Lesson {
    constructor() {
        this.hooks = {
            // 创建一个发布者对象
            vue: new SyncHook(["des"]),
        }
    }
    tap(){
        // 订阅消息
        this.hooks.vue.tap("zs", function (des) {
            console.log("zs", des);
        });
        this.hooks.vue.tap("ls", function (des) {
            console.log("ls", des);
        });
    }
    call(){
        // 发布消息
        this.hooks.vue.call("书到了");
    }
}
let ls = new Lesson();
ls.tap();
ls.call();

按照订阅的先后顺序执行所有的订阅函数(事件处理函数)。

SyncBailHook
const { SyncBailHook } = require('tapable');

class Lesson {
    constructor() {
        this.hooks = {
            // 创建一个发布者对象
            vue: new SyncBailHook(["des"]),
        }
    }
    tap(){
        // 订阅消息
        this.hooks.vue.tap("zs", function (des) {
            console.log("zs", des);
            // return "1";
            return undefined;
        });
        this.hooks.vue.tap("ls", function (des) {
            console.log("ls", des);
            // return "2";
        });
        this.hooks.vue.tap("ww", function (des) {
            console.log("ww", des);
            return "3";
        });
    }
    call(){
        // 发布消息
        this.hooks.vue.call("书到了");
    }
}
let ls = new Lesson();
ls.tap();
ls.call();

会按照订阅的先后顺序执行所有的事件处理函数,并且如果在执行的过程中有一个订阅函数返回的不是undefined, 就会停止执行后续函数。

SyncWaterfallHook
const { SyncWaterfallHook } = require('tapable');

class Lesson {
    constructor() {
        this.hooks = {
            // 创建一个发布者对象
            vue: new SyncWaterfallHook(["des"]),
        }
    }
    tap(){
        // 订阅消息
        this.hooks.vue.tap("zs", function (des) {
            console.log("zs", des);
            return "1";
        });
        this.hooks.vue.tap("ls", function (des) {
            console.log("ls", des);
            return "2";
        });
        this.hooks.vue.tap("ww", function (des) {
            console.log("ww", des);
            return "3";
        });
    }
    call(){
        // 发布消息
        this.hooks.vue.call("书到了");
    }
}
let ls = new Lesson();
ls.tap();
ls.call();

会按照订阅的先后顺序执行所有的事件处理函数,并且会将上一个订阅函数的返回值作为参数传递给下一个订阅函数。

AsyncParallelHook
const { AsyncParallelHook } = require('tapable');

class Lesson {
    constructor() {
        this.hooks = {
            // 创建一个发布者对象
            vue: new AsyncParallelHook(["des"]),
        }
        this.index = 0;
    }
    tap(){
        // 订阅消息
        this.hooks.vue.tapAsync("zs", (des, cb) => {
            setTimeout(function () {
                console.log("zs", des);
                cb();
            }, 3000);
        });
        this.hooks.vue.tapAsync("ls", function (des, cb) {
            setTimeout(function () {
                console.log("ls", des);
                // cb();
            }, 2000);
        });
        this.hooks.vue.tapAsync("ww", function (des, cb) {
            setTimeout(function () {
                console.log("ww", des);
                cb();
            }, 1000);
        });
    }
    call(){
        // 发布消息
        this.hooks.vue.callAsync("书到了", function () {
            console.log("end");
        });
    }
}
let ls = new Lesson();
ls.tap();
ls.call();

发出消息后,会同时执行所有的订阅函数,并且在执行订阅函数时会自动传递一个callback参数。
每个订阅函数执行完毕之后必须通过callback告诉系统订阅函数已经执行完毕了,当所有订阅函数都调用完callback后会通过回调函数的方式告callAsync全部执行完毕了。

(注意:若是异步并行钩子,订阅方法要使用tapAsync,发布方法要使用callAsync,即在tap和call方法后加上Async以示区分)

发布订阅模式的编写,插件tapable的使用至此结束。原理和使用很简单,也很重要,正如前面所说,很多框架都用到了它,这是编写插件的必备技能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值