简介
webpack事件机制是引用tapable库实现
tapable提供了同步和异步的钩子,通过tap(同步)、tapAsync(done回调式异步)、tapPromise(Promise式异步)注册事件,call,callAsync,promise执行事件
类图
Hook数据结构
class Hook {
constructor(args) {
if(!Array.isArray(args)) args = [];
// 把数组args赋值给 _args的内部属性
this._args = args;
// 保存所有的tap事件
this.taps = [];
// 拦截器数组: 用于修改监听函数的内容
this.interceptors = [];
// 调用 内部方法 _createCompileDelegate 然后把返回值赋值给内部属性 _call, 并且暴露给外部属性 call
this.call = this._call = this._createCompileDelegate("call", "sync");
/* 调用 内部方法 _createCompileDelegate ,然后把返回值赋值给内部属性 _promise,并且暴露外部属性 promise */
this.promise = this._promise = this._createCompileDelegate("promise", "promise");
/* 调用 内部方法 _createCompileDelegate,然后把返回值赋值给内部属性 _callAsync, 并且暴露外部属性 callAsync */
this.callAsync = this._callAsync = this._createCompileDelegate("callAsync", "async");
// 用于调用函数的时候,保存钩子数组的变量
this._x = undefined;
}
}
钩子定义
钩子即注册的监听函数
(1)Sync* :钩子为同步函数,即串行执行同步函数
1.SyncHook : 不关心监听函数的返回值
2.SyncBailHook : 有一个函数的返回值不为 `undefined`,则跳过剩下所有的逻辑
3.SyncWaterfallHook : 上一个函数的返回值可以传给下一个监听函数
4.SyncLoopHook : 如果函数返回`true`时则该监听函数会反复执行,如果返回 `undefined` 则表示退出循环
(2)Async* : 钩子为异步函数,即串行/并行执行异步函数
1.AsyncSeriesHook : 不关心`callback()`的参数
2.AsyncSeriesBailHook : 钩子函数返回非`undefined`,就会跳过后续函数,直接执行`callAsync`等触发函数绑定的回调函数
3.AsyncSeriesWaterfallHook : 上一个监听函数的中的`callback(err, data)`的第二个参数可以作为下一个监听函数的参数
4.AsyncSeriesLoopHook : 某一步钩子函数会循环执行到返回非undefined,才会开始下一个钩子。Hook回调会在所有钩子回调完成后执行
5.AsyncParallelHook: 不关心监听函数的返回值
6.AsyncParallelBailHook : 只要监听的返回值不为 `undefined`,就会忽略后续的函数,直接执行到`callAsync`绑定的回调函数
自实现tapable简易事件机制
class Hook {
// 扩展
constructor(args) {
this.syncTasks = []
this.asyncTasks = []
this.promiseTasks = []
}
tap(name, task) {
this.syncTasks.push(task)
}
tapAsync(name, task) {
this.asyncTasks.push(task)
}
tapPromise(name, task) {
this.promiseTasks.push(task)
}
}
(1)Sync*(同步钩子)
SyncHook
// 不关心监听函数的返回值
class SyncHook extends Hook {
call(...args) {
this.syncTasks.forEach((task) => task(...args))
}
}
SyncBailHook
// 有一个函数的返回值不为 `undefined`,则跳过剩下所有的逻辑
class SyncBailHook extends Hook {
call(...args) {
let ret, index = 0
do {
ret = this.syncTasks[index++](...args)
} while(ret === undefined && index < this.tasks.length)
}
}
SyncWaterfallHook
// 上一个函数的返回值可以传给下一个监听函数
class SyncWaterfallHook extends Hook {
call(...args) {
let [first, ...others] = this.syncTasks
others.reduce((ret, next) => {
return next(ret)
}, first(...args))
}
}
SyncLoopHook
// 直至返回undefined同步才执行下一个函数,否则重头开始执行
class SyncLoopHook extends Hook {
call(...args) {
let len = this.syncTasks.length
for (let i = 0; i < len; i++) {
let task = this.syncTasks[i]
if (task(...args) !== undefined) i = 0
}
}
}
(2)Async*
异步钩子; 简易执行方式区分了callback 和 promsie形式的注册和执行
- callback :注册的function参数为 (...args, cb)
- promsie :注册的function参数为 (...args)
AsyncSeriesHook
// 不关心`callback()`的参数
class AsyncSeriesHook extends Hook {
callAsync(...args) {
const len = this.asyncTasks.length
let callback = args.pop()
let index = 0
let next = async () => {
if (len === index) return callback()
let task = this.asyncTasks[index++]
task(...args, next)
}
next() // express
}
promise(...args) {
let [first, ...others] = this.promiseTasks
return others.reduce((p, n) => { // redux
return p.then(() => n(...args))
}, first(...args))
// 实现二:
// this.promiseTasks.reduce((p, n) => p.then(() => n(...args)), Promise.resolve())
}
}
AsyncSeriesBailHook
// 串行执行的钩子函数返回非`undefined`,就会跳过后续函数,直接执行`callAsync`等触发函数绑定的回调函数
class AsyncSeriesBailHook extends Hook {
callAsync(...args) {
const len = this.asyncTasks.length
let callback = args.pop()
let index = 0
let next = (val) => {
if (len === index || !!val) return callback()
let task = this.asyncTasks[index++]
task(...args, next)
}
next()
}
promise(...args) {
const [first, ...others] = this.promiseTasks
return new Promise((resolve, reject) => {
others.reduce((p, n) => p.then(() => n(...args)), first(...args))
})
}
}
AsyncSeriesWaterfallHook
// 上一个监听函数的中的`callback(err, data)`的第二个参数可以作为下一个监听函数的参数, 前钩子函数不会阻止后续函数执行
class AsyncSeriesWaterfallHook extends Hook {
callAsync(...args) {
let callback = args.pop()
let index = 0
let next = (err, ...data) => {
let task = this.asyncTasks[index++]
if (!task) return callback(err, ...data) // 不阻止后续执行
task(...data, next) // 即 注册的callback需要传递参数(err, data)
}
next(null, ...args)
}
promise(...args) {
let [first, ...others] = this.promiseTasks
return others.reduce((p, n) => {
return p.then((v) => n(v))
}, first(...args))
// 实现二:
// this.tasks.reduce((p, n) => p.then((v) => n(v)), Promise.resolve(...args))
}
}
AsyncSeriesLoopHook
// 如果函数返回非undefined,执行下一个钩子,否则从第一个函数开始执行
class AsyncSeriesLoopHook extends Hook {
callAsync(...args) {
let callback = args.pop()
let index = 0
let next = async (err, done) => {
if (done === undefined) index++
else index = 0
let task = this.asyncTasks[index]
if (!task) return callback(err)
task(...args, next)
}
next(null, true)
}
promise(...args) {
let [first, ...others] = this.promiseTasks
const fn = () => {
return others.reduce((p, n) => {
return p.then((v) => {
if (v === undefined) return n(v)
else return fn()
})
}, first(...args))
}
return fn()
}
}
AsyncParallelHook
// 同时执行异步函数, 不关心监听函数的返回值, 等所有函数执行完再执行一次callAsync注册的回调函数
class AsyncParallelHook extends Hook {
callAsync(...args) {
let callback = args.pop()
let index = 0, len = this.asyncTasks.length
let done = () => {
index++
if (index === len) callback()
}
this.asyncTasks.forEach(task => {
task(...args, done)
})
}
promise(...args) {
let callback = args.pop()
let tasks = this.promiseTasks.map(task => task(...args))
return await Promise.allSettled(tasks)
}
}
AsyncParallelBailHook
// callback执行顺序不确定?!
class AsyncParallelBailHook extends Hook {
// 所有done执行才执行callback
callAsync(...args) {
const callback = args.pop()
let allRes
let index = 0, len = this.asyncTasks.length
let done = () => {
index++
allRes && index === len && callback(allRes)
}
this.asyncTasks.forEach(task => {
let res = task(...args, done)
if (!allRes && res !== undefined) {
allRes = res
}
})
}
// 返回第一个(非undefined的resolve或reject返回)函数的处理结果
async promise(...args) {
return new Promsie((resolve, reject) => {
this.promiseTasks.forEach(task => {
task(...args).then(val => {
if (val !== undefined) {
resolve(val)
}
}).catch(errMessage => {
if (errMessage !== undefined) {
reject(emerrMessage)
}
})
})
})
}
}
【注】tapable中同步/异步监听函数为同一队列,即hook可按继承关系调用call, callAsync和promsie