原本这篇是要说 resolve 实现的,但我发现讲 resolve 以前不把 Tapable 讲清楚,是搞不明白,这篇是最重要的,其余能够跳过,这个但愿正在看的读者,仔细看看,搞明白了,下次面试,人家问你什么事件流,发布订阅,观察者模式啊,你把这篇吹一波,确定加分很多(额,牛皮仍是要吹的)javascript
先拉远些,咱们看三大框架,都有个生命周期的概念,在生命周期暴露些hook,生命周期说白了就是对一个流程的抽象,就像一个小河里的水流,通过一个个大坝,规定有的大坝处理下垃圾,有的来个分流到小河里灌溉,有的来防洪,哪一个大坝能够捕鱼,定义好每一个大坝要干的事,那周围的人就知道本身干什么事的时候,到哪一个大坝去就行了vue
当你在写一个复杂的逻辑的,特别是暴露给不少人用的库时,必定要先抽象出个生命周期的流程出来,会方便不少,扩展也容易不少(我在这方面就不足,常常一顿乱写,每次升级别人依赖个人东西时候,都得手动去改代码,灵活性不足,正在慢慢学习使用这个思惟)java
注意哈,这里的事件
不是单纯的指 dom元素的事件,指的是程序的逻辑的关系,nodejs 有个 EventEmitter ,EventEmitter 的核心函数有两个,向事件集合中添加函数的event.on()以及用于触发的函数event.emit()。这个本质上是个发布订阅,先列几个常见的业务场景node
上面是一些具体的业务场景,读者能够先想一想这些你遇到了会怎么解决webpack
咱们回到Webpack,Webpack 扩展了 EventEmitter ,把全部的逻辑抽象成各类 plugins ,要 打包 TypeScipt,加个对应的 loader,这些loader都是对应的 plugin,而后挂载上去,须要 处理编译后的文件,自定义个 new Plugin
来挂载,上面说的几个业务场景,Webpack 构建过程当中都有遇到,并且比上面复杂的多流程都有,那咱们理解了 Webpack 实现思想,在去处理 咱们工做中遇到的具体业务逻辑,就很是方便了,这也是网上说的,多看源码,理解源码,为本身所用的缘由git
事件流最大的好处就是代码耦合性小,把逻辑独立到个个子模块,可插拔web
Webpack 80%的代码都是经过 plugins 的方式挂载的,外带第三方 plugins,loader,同时经过 同样挂载,我数了下 Webpack文档,compiler暴露了 24 个 hook,compilation 暴露了 65 个 hook ,要把这么复杂的打包流程组装起来,webpack 就是基于事件流开发了一个独立的骨架,这个骨架就是 Tapable,Tapable就是Webpack的大脑面试
总于说到Tapable了,下面就具体看下 Tapable 把事件流定义了那几个类型,Tapable的讲解,分为3部分框架
先说说最简单的 sync 实现dom
SyncHook的特色是,每一个注入的方法不关注返回值,同步的串行的执行每一个方法
看段具体代码
const { SyncHook } = require("tapable");
class MyVue {
constructor() {
this.hooks = {
beforeCreate: new SyncHook(["beforeCreateHook"]),
created: new SyncHook(["createdHook"]),
mounted: new SyncHook(),
destroyed: new SyncHook(),
};
}
defaultBeforeCreateHook(){
this.hooks.beforeCreate.tap('1', (name) => {
console.log('default', name);
})
}
beforeCreate() {
console.log('准备初始化MyVue实例了')
//....这里框架干了一堆事 ,就经过 hook 把使用者注入代码执行完成了
this.hooks.beforeCreate.call('MyVue')
}
created() {
console.log('干点其余事,唤起hook created ')
this.hooks.created.call(this)
}
init() {
//..... 干一堆事
this.beforeCreate()
//..... 再干一堆事
this.created()
}
}
const vueInstance = new MyVue()
vueInstance.hooks.beforeCreate.tap('1', (name) => {
console.log('hello', name);
})
vueInstance.hooks.beforeCreate.tap('2', (name) => {
console.log('Wellocome', name);
})
vueInstance.init()
输出以下:
准备初始化MyVue实例了
hello MyVue
Wellocome MyVue
干点其余事,唤起hook created
复制代码
这里模仿 Vue 定义了几个生命周期,这样是否是就把代码解藕了,在本身项目中就能够经过这种方式,把外部传入的逻辑和本身框架的逻辑剥离出来, defaultBeforeCreateHook
方法就能够作到可插拔,在你框架逻辑变化的时候,就改变 default* 这个方法就好 咱们日常有写 Webpack 插件的话,都是这种方式挂载上去的
SyncHook 的特色是,每一个注入的方法不关注上个方法返回值,同步的串行的执行每一个方法,下面简单实现 SyncHook
class Hook{
constructor(args){
this.taps = []
this.interceptors = [] // 这个放在后面用
this._args = args
}
tap(name,fn){
this.taps.push({name,fn})
}
}
class SyncHook extends Hook{
call(name,fn){
try {
this.taps.forEach(tap => tap.fn(name))
fn(null,name)
} catch (error) {
fn(error)
}
}
}
复制代码
看到 this.taps.forEach(tap => tap.fn(name))
,这里不关注上个方法返回值
SyncWaterfallHook 和 SyncHook 的差异是 SyncWaterfallHook 注入的方法,上一个方法的返回值会作为下个方法的参数传入,这个和咱们最上面说的那个业务场景
复杂些的审批流程,下一个组件的状态依赖上一个组件的返回值数据
是否是就很合适的套上了,SyncWaterfallHook 实现以下
class SyncWaterfallHook extends Hook{
call(name,fn){
try {
let result = this.taps.reduce((result,tap) => tap.fn(name),null)
fn(null,result)
} catch (error) {
fn(error)
}
}
}
复制代码
假设有一方法返回非 undefined
,跳过剩下的 tap,当即执行回调,实现以下
class SyncBailHook extends Hook{
call(name,fn){
try {
let result = this.taps.reduce((result,tap) => {
if(result != undefined){
fn(null,result)
}else{
return tap.fn(name)
}
},null)
fn(null,result)
} catch (error) {
fn(error)
}
}
}
复制代码
这个Hook比较适合在 知足某些条件的状况下,执行某个逻辑,而这些条件是当下不能肯定的,须要使用者来定义,传入框架的,说的有点绕,我不知道怎么去描述这个场景,能够简单的想成,多条件判断,只要一个返回 true,就执行具体逻辑
若是当前方法返回 非undefined
,一直执行这个方法,只有当返回 undefined
跳出循环,执行下一个方法,这个我目前没有想到使用场景,实现方式以下
class SyncLoopHook extends Hook {
call(name, fn) {
try {
this.taps.forEach(tap => {
let result;
do {
result = tap.fn(name)
} while (result !== undefined);
})
fn(null)
} catch (error) {
fn(error)
}
}
}
复制代码
同步 hook 写完,理解学会这几种用法,在日常工做中能够大大的减小代码耦合,值得深刻理解学习 下篇咱们讲下异步Hook