在上面代码的 call 方法中,我们设置返回值为 ret ,第一次执行后没有返回值则继续循环执行,如果有返回值则立即停止循环,即实现 “保险” 的功能。
3、SyncWaterfallHook
SyncWaterfallHook 为串行同步执行,上一个事件处理函数的返回值作为参数传递给下一个事件处理函数,依次类推,正因如此,只有第一个事件处理函数的参数可以通过 call 传递,而 call 的返回值为最后一个事件处理函数的返回值。
// SyncWaterfallHook 钩子的使用 const { SyncWaterfallHook } = require("tapable"); // 创建实例 let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]); // 注册事件 syncWaterfallHook.tap("1", (name, age) => { console.log("1", name, age); return "1"; }); syncWaterfallHook.tap("2", data => { console.log("2", data); return "2"; }); syncWaterfallHook.tap("3", data => { console.log("3", data); return "3" }); // 触发事件,让监听函数执行 let ret = syncWaterfallHook.call("panda", 18); console.log("call", ret); // 1 panda 18 // 2 1 // 3 2 // call 3
SyncWaterfallHook 名称中含有 “瀑布”,通过上面代码可以看出 “瀑布” 形象生动的描绘了事件处理函数执行的特点,与 SyncHook 和 SyncBailHook 的区别就在于事件处理函数返回结果的流动性,接下来看一下 SyncWaterfallHook 类的实现。
// 模拟 SyncWaterfallHook 类 class SyncWaterfallHook { constructor(args) { this.args = args; this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(...args) { // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined args = args.slice(0, this.args.length); // 依次执行事件处理函数,事件处理函数的返回值作为下一个事件处理函数的参数 let [first, ...others] = this.tasks; return reduce((ret, task) => task(ret), first(...args)); } }
上面代码中 call 的逻辑是将存储事件处理函数的 tasks 拆成两部分,分别为第一个事件处理函数,和存储其余事件处理函数的数组,使用 reduce 进行归并,将第一个事件处理函数执行后的返回值作为归并的初始值,依次调用其余事件处理函数并传递上一次归并的返回值。
4、SyncLoopHook
SyncLoopHook 为串行同步执行,事件处理函数返回 true 表示继续循环,即循环执行当前事件处理函数,返回 undefined 表示结束循环, SyncLoopHook 与 SyncBailHook 的循环不同, SyncBailHook 只决定是否继续向下执行后面的事件处理函数,而 SyncLoopHook 的循环是指循环执行每一个事件处理函数,直到返回 undefined 为止,才会继续向下执行其他事件处理函数,执行机制同理。
// SyncLoopHook 钩子的使用 const { SyncLoopHook } = require("tapable"); // 创建实例 let syncLoopHook = new SyncLoopHook(["name", "age"]); // 定义辅助变量 let total1 = 0; let total2 = 0; // 注册事件 syncLoopHook.tap("1", (name, age) => { console.log("1", name, age, total1); return total1++ < 2 ? true : undefined; }); syncLoopHook.tap("2", (name, age) => { console.log("2", name, age, total2); return total2++ < 2 ? true : undefined; }); syncLoopHook.tap("3", (name, age) => console.log("3", name, age)); // 触发事件,让监听函数执行 syncLoopHook.call("panda", 18); // 1 panda 18 0 // 1 panda 18 1 // 1 panda 18 2 // 2 panda 18 0 // 2 panda 18 1 // 2 panda 18 2 // 3 panda 18
通过上面的执行结果可以清楚的看到 SyncLoopHook 的执行机制,但有一点需要注意,返回值必须严格是 true 才会触发循环,多次执行当前事件处理函数,必须严格返回 undefined ,才会结束循环,去执行后面的事件处理函数,如果事件处理函数的返回值不是 true 也不是 undefined ,则会死循环。
在了解 SyncLoopHook 的执行机制以后,我们接下来看看 SyncLoopHook 的 call 方法是如何实现的。
// 模拟 SyncLoopHook 类 class SyncLoopHook { constructor(args) { this.args = args; this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(...args) { // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined args = args.slice(0, this.args.length); // 依次执行事件处理函数,如果返回值为 true,则继续执行当前事件处理函数 // 直到返回 undefined,则继续向下执行其他事件处理函数 this.tasks.forEach(task => { let ret; do { ret = this.task(...args); } while (ret === true || !(ret === undefined)); }); } }