用200 行代码,简单写一下 tapable

61 篇文章 0 订阅
61 篇文章 0 订阅

tapable 的钩子函数根据事件运行机制来分可以划分成四种:串行机制、中断机制、流水线机制和循环机制;根据运行模式可以划分成同步和异步,同时异步又可分为异步串行异步并行

只有串行情况下,才能实现流水线机制,所以异步并行没有流水线机制的钩子。同时并行的情况下其实循环机制也难以控制,所有异步并行也没有循环机制的钩子。

同步钩子(同步串行)

  • SyncHook(串行)
  • SyncBailHook(中断)
  • SyncWaterfallHook(流水线)
  • SyncLoopHook(循环)

异步钩子

异步分为异步串行异步并行两种。

异步串行

  • AsyncSeriesHook(串行)
  • AsyncSeriesBailHook(中断)
  • AsyncSeriesWaterfallHook(流水线)
  • AsyncSeriesLoopHook(循环)

异步并行

  • AsyncParallelHook
  • AsyncParallelBailHook(中断)

基本使用(以 SyncHook 为例)

需要注意的有以下两点:

  • 实例化钩子的时候限制了参数数量,后面即使调用方(hook.call)和接收方(hook.tap)约定好参数数量,hook.tap 实际上也不会收到多出的参数。
  • hook.tap 可以注册多个事件,多个事件会按注册顺序执行,hook.call 可以调用多次。

同步钩子

同步钩子使用同步的订阅方式 hook.tap 以及同步的触发方式 hook.call

SyncHook

这个钩子最简单,它只是按照事件的注册顺序依次调用,所注册的事件之间相互独立,没有任何关联。

SyncBailHook

这个钩子与 SyncHook 类似,也是顺序调用注册事件。但不同的是:它多了一种中断机制,也就是当前一个事件返回值 不是 undefined 时,会中断后面事件的执行。

SyncWaterfallHook

这个钩子与 SyncHook 类似,也是顺序调用注册事件。但不同的是,它加入了流水线设计,像流水一样,上流会影响下流(也就是上一个事件的 return 会作为下一个事件的参数)。

上一个事件的返回值会覆盖第一个参数

SyncLoopHook

这个钩子相对是最复杂的一个钩子,它设计了一种循环规则,实现注册事件的循环调用。那么这种循环规则就是:先顺序执行注册事件,当事件返回值不是 undefined 时,再从头开始执行注册事件,直到所有事件都返回 undefined 时,循环结束

异步钩子

异步钩子采用异步的订阅方式和异步的触发方式,而我们知道 js 异步编程总共有两种解决方案:callback 和 promise。所有 tapable 也提供这两种方式的订阅钩子:hook.tapAsynchook.tapPromise,以及触发方式:hook.callAsynchook.promise

异步串行

AsyncSeriesHook

剩余三个与同步的相同机制类似,这里不再赘述。

异步并行

AsyncParallelBailHook

异步并行我们只需要看一眼 AsyncParallelBailHook 这个钩子即可,它是只要有一个事件达到中断条件,整体结果就完成了。

可以看到当 done2 结束时,return 了一个 undefined,所以并没有达到中断条件。过了 1s 后,done1 结束 return 了一个非 undefined,达到中断条件,于是中断完成,也就是 done 输出在 [ 'a' ] done3 之前。

简单实现

了解完了 tapable 有这十种钩子,我们可以简单来实现一下。

同步串行

简单分析一下,很显然事件的订阅方法和钩子的触发方法是相同的,这一块我们可以提取到一个公共类中 Hooks。如下:

而钩子触发之后具体的事件调用逻辑是各不相同的,所以 execute 方法可以在各个子类中去实现。代码如下:

异步串行

实现了同步串行的四个钩子之后,再实现异步串行的四个钩子其实就十分简单了,只需要 await 一下即可。我们先在基础 Hooks 上增加两个订阅事件和触发事件方法,如下:

call 方法可以复用,需要改造一下,因为异步钩子需要多接收一个 callback 参数。但是订阅的时候,我们需要记录一下订阅的类型,因为 callback 和 promise 的等待方式不一样。

其他的流程是一样的,只需要注意 await,代码如下:

异步并发

最后我们再实现两个异步并发的钩子。对于 AsyncParallelHook 我们只需要使用 Promise.all 装一下即可,代码如下:

对于 AsyncParallelBailHook 钩子,只要出现有一个结果不为 undefined,那么就结束,所以我们使用 Promise.any(这个方法会等到第一个成功才结束,利用这种特性,当返回值不为 undefined 的时候,我们可以手动构造一个 reject) 包一下。代码如下:

其实到目前位置 tapable 的所有钩子就基本实现了,当然这是十分简易的版本,还有一些错误处理和细枝末节,我们就不处理了。

小疑问

如果看过 tapable 源码的朋友可能知道,我们如下的源代码:

会被编译成如下代码,再进去运行:

那么为什么 tapable 要这样做呢?

有兴趣的朋友可以看看这个 issues:Is the new Function performance really good?。我大概看了一下,大概意思是:当数量比较大时,浏览器会进行优化,使用 new Function 的方式会占一点点优势。正如下图:

133262670-de083776-13bd-472d-baa1-97098773ee12.png

附录(完整代码)

原文链接:https://juejin.cn/post/7354940462061207593

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值