记录下自己在前端路上爬坑的经历 加深印象,正文开始~tapable是webpack的核心依赖库 想要读懂webpack源码 就必须首先熟悉tapableok.下面是webapck中引入的tapable钩子 由此可见 在webpack中tapable的重要性const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require(“tapable”);这些钩子可分为同步的钩子和异步的钩子,Sync开头的都是同步的钩子,Async开头的都是异步的钩子。而异步的钩子又可分为并行和串行的钩子,其实同步的钩子也可以理解为串行的钩子。本文将以以下章节分别梳理每个钩子同步钩子SyncHookSyncBailHookSyncWaterfallHookSyncLoopHook异步钩子AsyncParallelHookAsyncParallelHook的Promise版本AsyncSeriesHookAsyncSeriesHook的Promise版本AsyncSeriesWaterfallHookAsyncSeriesWaterfallHook的Promise版本首先安装tapable npm i tapable -DSyncHookSyncHook是简单的同步钩子,它很类似于发布订阅。首先订阅事件,触发时按照顺序依次执行,所以说同步的钩子都是串行的。const { SyncHook } = require(’tapable’);class Hook{ constructor(){ /** 1 生成SyncHook实例 / this.hooks = new SyncHook([’name’]); } tap(){ /* 2 注册监听函数 / this.hooks.tap(’node’,function(name){ console.log(’node’,name); }); this.hooks.tap(‘react’,function(name){ console.log(‘react’,name); }); } start(){ /* 3出发监听函数 / this.hooks.call(‘call end.’); }}let h = new Hook();h.tap();/* 类似订阅 / h.start();/* 类似发布 // 打印顺序: node call end. react call end./可以看到 它是按照顺序依次打印的,其实说白了就是发布和订阅。接下来我们就手动实现它。class SyncHook{ // 定义一个SyncHook类 constructor(args){ / args -> [’name’]) / this.tasks = []; } /* tap接收两个参数 name和fn / tap(name,fn){ /* 订阅:将fn放入到this.tasks中 / this.tasks.push(fn); } start(…args){/* 接受参数 / /* 发布:将this.taks中的fn依次执行 / this.tasks.forEach((task)=>{ task(…args); }); }}let h = new SyncHook([’name’]);/* 订阅 /h.tap(‘react’,(name)=>{ console.log(‘react’,name);});h.tap(’node’,(name)=>{ console.log(’node’,name);});/* 发布 /h.start(’end.’);/ 打印顺序: react end. node end./SyncBailHookSyncBailHook 从字面意思上理解为带有保险的同步的钩子,带有保险意思是 根据每一步返回的值来决定要不要继续往下走,如果return了一个非undefined的值 那就不会往下走,注意 如果什么都不return 也相当于return了一个undefined。const { SyncBailHook } = require(’tapable’);class Hook{ constructor(){ this.hooks = new SyncBailHook([’name’]); } tap(){ this.hooks.tap(’node’,function(name){ console.log(’node’,name); /** 此处return了一个非undefined * 代码到这里就不会继续执行余下的钩子 / return 1; }); this.hooks.tap(‘react’,function(name){ console.log(‘react’,name); }); } start(){ this.hooks.call(‘call end.’); }}let h = new Hook();h.tap();h.start();/ 打印顺序: node call end./手动实现class SyncHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(…args){ let index = 0; let result; /** 利用do while先执行一次的特性 / do{ /* 拿到每一次函数的返回值 result / result = this.tasksindex++; /* 如果返回值不为undefined或者执行完毕所有task -> 中断循环 / }while(result === undefined && index < this.tasks.length); }}let h = new SyncHook([’name’]);h.tap(‘react’,(name)=>{ console.log(‘react’,name); return 1;});h.tap(’node’,(name)=>{ console.log(’node’,name);});h.start(’end.’);/ 打印顺序: react end./SyncWaterfallHookSyncWaterfallHook是同步的瀑布钩子,瀑布怎么理解呢? 其实就是说它的每一步都依赖上一步的执行结果,也就是上一步return的值就是下一步的参数。const { SyncWaterfallHook } = require(’tapable’);class Hook{ constructor(){ this.hooks = new SyncWaterfallHook([’name’]); } tap(){ this.hooks.tap(’node’,function(name){ console.log(’node’,name); /** 此处返回的值作为第二步的结果 / return ‘第一步返回的结果’; }); this.hooks.tap(‘react’,function(data){ /* 此处data就是上一步return的值 / console.log(‘react’,data); }); } start(){ this.hooks.call(‘callend.’); }}let h = new Hook();h.tap();h.start();/ 打印顺序: node callend. react 第一步返回的结果/手动实现:class SyncWaterFallHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(…args){ /** 解构 拿到tasks中的第一个task -> first / let [first, …others] = this.tasks; /* 利用reduce() 累计执行 * 首先传入第一个 first 并执行 * l是上一个task n是当前task * 这样满足了 下一个函数依赖上一个函数的执行结果 / others.reduce((l,n)=>{ return n(l); },first(…args)); }}let h = new SyncWaterFallHook([’name’]);/* 订阅 /h.tap(‘react’,(name)=>{ console.log(‘react’,name); return ‘我是第一步返回的值’;});h.tap(’node’,(name)=>{ console.log(’node’,name);});/* 发布 /h.start(’end.’);/ 打印顺序: react end. node 我是第一步返回的值*/SyncLoopHookSyncLoopHook是同步的循环钩子。 循环钩子很好理解,就是在满足一定条件时 循环执行某个函数:const { SyncLoopHook } = require(’tapable’);class Hook{ constructor(){ /** 定义一个index / this.index = 0; this.hooks = new SyncLoopHook([’name’]); } tap(){ /* 箭头函数 绑定this / this.hooks.tap(’node’,(name) => { console.log(’node’,name); /* 当不满足条件时 会循环执行该函数 * 返回值为udefined时 终止该循环执行 / return ++this.index === 5?undefined:‘学完5遍node后再学react’; }); this.hooks.tap(‘react’,(data) => { console.log(‘react’,data); }); } start(){ this.hooks.call(‘callend.’); }}let h = new Hook();h.tap();h.start();/ 打印顺序: node callend. node callend. node callend. node callend. node callend. react callend./可以看到 执行了5遍node callend后再继续往下执行。也就是当返回undefined时 才会继续往下执行:class SyncWaterFallHook{ constructor(args){ this.tasks = []; } tap(name,fn){ this.tasks.push(fn); } start(…args){ let result; this.tasks.forEach((task)=>{ /** 注意 此处do{}while()循环的是每个单独的task / do{ /* 拿到每个task执行后返回的结果 / result = task(…args); /* 返回结果不是udefined时 会继续循环执行该task / }while(result !== undefined); }); }}let h = new SyncWaterFallHook([’name’]);let total = 0;/* 订阅 /h.tap(‘react’,(name)=>{ console.log(‘react’,name); return ++total === 3?undefined:‘继续执行’;});h.tap(’node’,(name)=>{ console.log(’node’,name);});/* 发布 /h.start(’end.’);/ 打印顺序: react end. react end. react end. node end./可以看到, 执行了3次react end.后 才继续执行了下一个task -> node end。至此,我们把tapable的所有同步钩子都解析完毕. 异步钩子比同步钩子麻烦些,我们会在下一章节开始解析异步的钩子.