Skip to content

tapable同步方法

上一节学习了tapable的一个同步钩子,SyncHook,这节来学习另外几个同步的钩子,它们各有各的特点。

SyncHook

上一节学的钩子,先注册任务到一个数组中,然后按顺序依次执行。

SyncBailHook

同步保险钩子。写同步的时候增加一个保险,这个保险一般称之为熔断性,当我们前一个事件执行完成后,可以决定是否继续向下执行。

增加一个保险。

引用tapable中的SyncBailHook

当其中一个任务返回非undefined的值时,就不会继续向下执行了

js
// webpack\webpack-tapable\2.start.js

// 同步保险钩子 SyncBailHook tapable
// 当其中一个任务返回非undefined的值时,就不会继续向下执行了
let { SyncBailHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new SyncBailHook(['name']), // 可能会传一个参数,参数是可选的
        }
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        this.hook.arch.tap('vue', function (name) {
            console.log('vue 111', name);

            return '学不下去了,停止学习'
            // SyncBailHook 同步保险钩子,当有一个任务返回一个非undefined的值,就不会继续向下执行了
        }) // 拿到钩子实例,实例上就有一个tap方法

        this.hook.arch.tap('react', function (name) {
            console.log('react 111', name);
        })

        // 001 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.call('cheny') // 实例上有一个call 方法,执行注册的事件
        // 002 调用这个方法时,会让数组中注册的方法依次执行
    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

打印输出结果,发现符合我们的期望,学习完vue之后,就不会继续向下学习react了

image-20220129093620020

假如返回undefined,就会继续向下执行了。

自己实现SyncBailHook

当注册的任务有一个返回非undefined的值时,就不继续向下执行

js
// webpack\webpack-tapable\2.case.js

// 当注册的任务有一个返回非undefined的值时,就不继续向下执行
// 相当于增加了一个保险
class SyncBailHook { // 同步保险钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 同步注册事件
    tap(name, task) {
        this.tasks.push(task)
    }


    // 按顺序依次执行注册过的事件
    // 如果其中一个返回非undefined的值后,就不继续向下执行了
    call(...args) {
        let ret; // 当前这个函数的返回值
        let index = 0; // 当前执行到第几个函数了,如果超出范围,说明执行完毕,结束循环

        // 使用do while 至少执行一次
        do {
            ret = this.tasks[index](...args)
            index++
        } while (ret === undefined && index < this.tasks.length);
    }
}


let hook = new SyncBailHook(['name']) // 参数可以传可以不传

// 注册事件
hook.tap('vue', function (name) {
    console.log('vue 1111', name);
    // 假设学习完这个就不想再继续往下学了
    // 那么就让这个任务返回非undefined的值
    return '就学到这吧,不继续向下学了'
})
hook.tap('react', function (name) {
    console.log('react 1111', name);
})

// 依次执行
hook.call('cheny')

测试一下,是我们想要的结果,当学习完vue就不想继续往下学了。

image-20220129094452705

SyncWaterfallHook

同步瀑布钩子。上一个任务的返回值,会被下一个任务接收到。就像一个瀑布一样,可以知道上一个任务的执行结果。

就是假如学习完vue之后,返回一个学习状态,然后再学习react时,我们知道vue学的好不好。

这就是同步瀑布钩子的应用场景。

其实就是一个流程控制。

引用tapable中的SyncWaterfallHook

当前任务的返回结果会被下一个任务接收到。

js
// webpack\webpack-tapable\3.start.js

// 同步瀑布钩子 SyncWaterfallHook tapable
// 当前任务的返回值,会被下一个任务接收到
let { SyncWaterfallHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new SyncWaterfallHook(['name']), // 可能会传一个参数,参数是可选的
        }
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        this.hook.arch.tap('vue', function (name) {
            console.log('vue 111', name);

            return 'vue 学的还不错'
            // SyncWaterfallHook 同步瀑布钩子
            // 当前任务的返回值,会被下一个任务接收到
        }) // 拿到钩子实例,实例上就有一个tap方法

        this.hook.arch.tap('react', function (data) {
            // data就会接收到上一个任务的返回结构
            console.log('react 111', data);
        })

        // 001 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.call('cheny') // 实例上有一个call 方法,执行注册的事件
        // 002 调用这个方法时,会让数组中注册的方法依次执行
    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

查看输出结构,vue学完之后的状态被react接收到了,是我们想要的结果。

image-20220129095156230

自己实现SyncWaterfallHook

上一个函数的结果,是这个函数的参数

js
// webpack\webpack-tapable\3.case.js

// 当前任务的返回结果,会被下一个任务接收到
class SyncWaterfallHook { // 同步瀑布钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 同步注册事件
    tap(name, task) {
        this.tasks.push(task)
    }


    // 按顺序依次执行注册过的事件
    // 当前任务的返回结果,会被下一个任务接收到
    call(...args) {
        let [first, ...others] = this.tasks
        let ret = first(...args)

        // 上一个函数的结果,是下一个函数的参数
        others.reduce((pre, cur) => {
            return cur(pre)
        }, ret)
    }
}


let hook = new SyncWaterfallHook(['name']) // 参数可以传可以不传

// 注册事件
hook.tap('vue', function (name) {
    console.log('vue 1111', name);
    // 当前任务的返回结果,会被下个任务接收到
    return 'vue学的还不错'
})
hook.tap('react', function (data) {
    console.log('react 1111', data);
    // 当前任务的返回结果,会被下个任务接收到
    return 'react学的一般'
})
hook.tap('webpack', function (data) {
    console.log('webpack 1111', data);
})

// 依次执行
hook.call('cheny')

我们看一下结果,是我们想要的

image-20220129095932165

SyncLoopHook

同步循环钩子。这个比较怪异,可以让某个任务执行多次之后,再去执行下一个。

webpack中并没有用到这个方法。

引用tapable中的SyncLoopHook

碰到某个任务时,循环执行。

当任务返回undefined时,放行,不返回undefined就一直执行。

所以可以使用一个count计数器来控制执行次数。

js
// webpack\webpack-tapable\4.start.js

// 同步循环钩子 SyncLoopHook tapable
// 某个任务执行多次后,再执行下一个
// 遇到某个不返回undefined的监听函数,会多次执行
let { SyncLoopHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new SyncLoopHook(['name']), // 可能会传一个参数,参数是可选的
        }

        this.count = 0 // 需要循环执行任务的执行次数
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        this.hook.arch.tap('vue', (name) => {
            console.log('vue 111', name);

            return ++this.count === 3 ? undefined : '继续学'
            // SyncLoopHook 同步循环钩子
            // 当前任务执行多次后,再去执行下一个
            // 不返回undefined 就一直执行
            // count变为3后,再让它返回undefined,放行
        }) // 拿到钩子实例,实例上就有一个tap方法

        this.hook.arch.tap('react', (name) => {
            console.log('react 111', name);
        })

        // 001 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.call('cheny') // 实例上有一个call 方法,执行注册的事件
        // 002 调用这个方法时,会让数组中注册的方法依次执行
    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

看一下输出结果,我们希望vue学3遍之后,再放行,使我们想要的结果。

image-20220129101057772

自己实现SyncLoopHook

js
// webpack\webpack-tapable\4.case.js

// 当前任务如果不返回undefined,就会一直执行
// 只有当返回undefined时,才会放行
// 可以使用一个计数器count,控制任务执行次数
// 当执行次数为期望值时,让函数返回undefined,放行
class SyncLoopHook { // 同步循环钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 同步注册事件
    tap(name, task) {
        this.tasks.push(task)
    }


    // 按顺序依次执行注册过的事件
    // 某个任务不返回undefined时,就一直执行
    call(...args) {
        // 首先 所有的任务都要执行一遍
        this.tasks.forEach(task => {
            // 如果某个任务不返回undefined 就一直执行
            let ret; // 当前任务的返回值
            do {
                ret = task(...args)
            } while (ret !== undefined);
        })
    }
}


let hook = new SyncLoopHook(['name']) // 参数可以传可以不传

// 注册事件
let count = 0; // 计数器,控制任务执行次数
hook.tap('vue', function (name) {
    console.log('vue 1111', name);
    // 需要学习3遍,每次执行count加1,当count为3时,让函数返回undefined,放行
    return ++count === 3 ? undefined : '继续学'
})
hook.tap('react', function (name) {
    console.log('react 1111', name);
})
hook.tap('webpack', function (name) {
    console.log('webpack 1111', name);
})

// 依次执行
hook.call('cheny')

看一下结果,希望vue学习3遍后,在执行下面的任务,输出结果符合期望

image-20220129102346233

总结

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来。

而实现这一切的核心就是Tapable。

Tapable有点类似于nodejs的events库,核心原理也是依赖发布订阅模式。

其中一共有四个同步钩子,分别有不同的执行效果。

  1. SyncHook

    最基础的同步钩子,按顺序将任务注册到数组中,然后再依次执行。

    js
    call(...args) {
        this.tasks.forEach((task) => {
            task(...args)
        })
    }
  2. SyncBailHook

    同步保险钩子。增加一道保险,按顺序执行任务时,会对返回值做判断,如果返回值不为undefined,就不继续向下执行了。

    js
    // 按顺序依次执行注册过的事件
    // 如果其中一个返回非undefined的值后,就不继续向下执行了
    call(...args) {
        let ret; // 当前这个函数的返回值
        let index = 0; // 当前执行到第几个函数了,如果超出范围,说明执行完毕,结束循环
    
        // 使用do while 至少执行一次
        do {
            ret = this.tasks[index](...args)
            index++
        } while (ret === undefined && index < this.tasks.length);
    }
  3. SyncWaterfallHook

    同步瀑布钩子。依次执行注册任务,当前任务的返回值,会作为下一个任务的参数。相当于把每个任务的执行状态依次传递了下去。

    js
    // 按顺序依次执行注册过的事件
    // 当前任务的返回结果,会被下一个任务接收到
    call(...args) {
        let [first, ...others] = this.tasks
        let ret = first(...args)
    
        // 上一个函数的结果,是下一个函数的参数
        others.reduce((pre, cur) => {
            return cur(pre)
        }, ret)
    }
  4. SyncLoopHook

    同步循环钩子。webpack中并没有使用这个钩子,不过也挺有意思的。

    依次执行注册任务,当某个任务返回值不为undefined时,就一直循环执行该任务。

    js
    // 按顺序依次执行注册过的事件
    // 某个任务不返回undefined时,就一直执行
    call(...args) {
        // 首先 所有的任务都要执行一遍
        this.tasks.forEach(task => {
            // 如果某个任务不返回undefined 就一直执行
            let ret; // 当前任务的返回值
            do {
                ret = task(...args)
            } while (ret !== undefined);
        })
    }

    所以使用的时候,可以用个计数器,控制执行次数。

    js
    // 注册事件
    let count = 0; // 计数器,控制任务执行次数
    hook.tap('vue', function (name) {
        console.log('vue 1111', name);
        // 需要学习3遍,每次执行count加1,当count为3时,让函数返回undefined,放行
        return ++count === 3 ? undefined : '继续学'
    })

参考

https://www.bilibili.com/video/BV1a4411e7Bz?p=29&spm_id_from=pageDriver