导航
[[深刻01] 执行上下文](https://juejin.im/post/684490...
[[深刻02] 原型链](https://juejin.im/post/684490...
[[深刻03] 继承](https://juejin.im/post/684490...
[[深刻04] 事件循环](https://juejin.im/post/684490...
[[深刻05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...
[[深刻06] 隐式转换 和 运算符](https://juejin.im/post/684490...
[[深刻07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...
[[深刻08] 前端平安](https://juejin.im/post/684490...
[[深刻09] 深浅拷贝](https://juejin.im/post/684490...
[[深刻10] Debounce Throttle](https://juejin.im/post/684490...
[[深刻11] 前端路由](https://juejin.im/post/684490...
[[深刻12] 前端模块化](https://juejin.im/post/684490...
[[深刻13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490...
[[深刻14] canvas](https://juejin.im/post/684490...
[[深刻15] webSocket](https://juejin.im/post/684490...
[[深刻16] webpack](https://juejin.im/post/684490...
[[深刻17] http 和 https](https://juejin.im/post/684490...
[[深刻18] CSS-interview](https://juejin.im/post/684490...
[[深刻19] 手写Promise](https://juejin.im/post/684490...
[[深刻20] 手写函数](https://juejin.im/post/684490...
[[react] Hooks](https://juejin.im/post/684490...
[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue我的项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...
[[源码-webpack01-前置常识] AST形象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置常识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简略编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...
[[源码-vue02] computed 响应式 - 初始化,拜访,更新过程 ](https://juejin.im/post/684490...
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...
[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...
前置常识
一些单词
bail:保险,保障parallel:并行的series:串行的,间断的optional:可选的// All Hook constructors take one optional argument, which is a list of argument names as strings.// 所有的 Hook 构造函数都接管一个可选的参数,是一个参数名称字符串组成的数组practice:实际// The best practice is to expose all hooks of a class in a hooks property// 最佳实际是在hooks属性中公开类的所有钩子accelerate:减速brake:刹车waterfall:瀑布
公布订阅模式
- 发布者:publisher
- 订阅者:subscribe
- 中介:topic/event channel
( <font color=red>中介</font> ) 既要接管 ( <font color=red>发布者</font> ) 公布的音讯,又要将音讯派发给订阅了该事件的 ( <font color=red>订阅者</font> )
- ( 中介 ) 须要依据 ( 不同的事件 ),贮存相应的 ( 订阅者 ) 信息
- 通过 ( 中介对象 ) 齐全 ( 解耦 ) 了 ( 发布者 ) 和 ( 订阅者 )
公布订阅模式代码实现 - es5
// 公布订阅模式<script>const pubsub = {};// pubsub中介对象// pubsub中具备subscribe,unSubscribe,publish等办法(function(pubsub) { const topics = {}; // topics是一个 ( 事件 ) 和订阅该事件的 ( 订阅者 ) 对应关系的一个对象 // key:是不同的事件 // value:是订阅了该事件的订阅者的数组 // event_name: [{fname: fn.name, fn}] // 订阅 pubsub.subscribe = function(eventName, fn) { // 不存在该事件对应的订阅者对象数组, 新建 // 存在,向数组中增加订阅者对象 if (!topics[eventName]) { topics[eventName] = [] } topics[eventName].push({ fname: fn.name, fn }) } // 公布 pubsub.publish = function(eventName, params) { if (!topics[eventName].length) { return } // 取出所有订阅者中的更新函数,并执行 topics[eventName].forEach((item, index) => { item.fn(params) }) } // 勾销订阅 pubsub.unSubscribe = function(eventName, fname) { if (!topics[eventName]) { return } topics[eventName].forEach((item, index) => { if (item.fname === fname) { topics[eventName].splice(index, 1) // 删除该事件对应的订阅者对象的更新函数同名的订阅者对象 } }) }})(pubsub)function goFn1(params) { console.log('goFn1', params)}function goFn2(params) { console.log('goFn2', params)}pubsub.subscribe('go', goFn1) // 订阅pubsub.subscribe('go', goFn2)pubsub.publish('go', '1111111') // 公布pubsub.unSubscribe('go', goFn2.name) // 勾销订阅pubsub.publish('go', '22222')</script>
公布订阅模式代码实现 - es6
<script>class PubSub { // PubSub类 constructor() { this.topics = {} // 保护事件和订阅者对应关系的对象 // 事件 <-> 订阅者数组 } subscribe(eventName, fn) { // 订阅 if (!this.topics[eventName]) { this.topics[eventName] = [] } this.topics[eventName].push({ fname: fn.name, fn }) } publish(eventName, params) { // 公布 if (!this.topics[eventName].length) { return } this.topics[eventName].forEach((item, index) => { item.fn(params) }) } unSubscribe(eventName, fname) { // 勾销订阅 if (!this.topics[eventName]) { return } this.topics[eventName].forEach((item, index) => { if (item.fname === fname) { this.topics[eventName].splice(index, 1) } }) }}const pubsub = new PubSub()function goFn1(params) { console.log('goFn1', params)}function goFn2(params) { console.log('goFn2', params)}pubsub.subscribe('go', goFn1)pubsub.subscribe('go', goFn2)pubsub.publish('go', '1') // 公布pubsub.unSubscribe('go', goFn2.name) // 勾销订阅pubsub.publish('go', '2')</script>
Tapable
- 外围就是公布订阅模式
- 装置:
npm install tapable -S
SyncHook
- 同步形式的公布订阅模式
const synchook = new SyncHook(['params'])
synchook.tap() // 订阅,注册
synchook.call() // 公布,执行
const { SyncHook } = require('tapable')class Work {constructor() { this.hooks = { city: new SyncHook(['who']) // 这里要求在订阅事件时的回调中须要传参 }}// 订阅,注册tap(eventName) { this.hooks.city.tap(eventName, function(who){ console.log(who, eventName) })}// 公布,执行publish() { this.hooks.city.call('woow_wu7')}}const work = new Work()work.tap('chongqing') // 订阅work.tap('hangzhou')work.publish() // 公布
--------- 比照参考const { SyncHook } = require('tapable')class Lesson {constructor() { this.hook = { arch: new SyncHook(['name']) // 同步钩子,在 .tap()函数的参数函数中接管一个参数 }}tap() { this.hook.arch.tap('react', (name) => { // name参数是在 .call()时传入的 console.log('react', name) }) this.hook.arch.tap('vue', (name) => { console.log('vue', name) })}publish() { this.hook.arch.call('woow_wu7')}}const lesson = new Lesson(['name'])lesson.tap() // 注册lesson.publish() // 公布
SyncHook模仿实现
class SyncHook { constructor() { this.observers = [] // observers 是观察者对象组成的数组,观察者对象中蕴含event事件名称, 和fn工作函数 // [{event:eventName, fn: fn}] } // 注册观察者对象 // 更简略点能够间接注册事件 tap(eventName, fn) { this.observers.push({ event: eventName, fn }) } // 执行注册的观察者对象中的fn函数 call(...params) { this.observers.forEach(item => item.fn(item.event, ...params)) }}const synchook = new SyncHook(['params'])synchook.tap('react', function(eventName, name) { console.log(eventName, name)})synchook.tap('vue', function(eventName, name) { console.log(eventName, name)})synchook.call('woow_wu7')
SyncBailHook
- SyncBailHook提供了 ( 终止执行 ) 订阅者数组中监听函数的机制
当 .tap() 中的监听函数返回值是 ( <font color=red>!undefined</font> ) 时会 ( <font color=red>终止执行</font> ) .call() 函数
let { SyncBailHook } = require('tapable');class Lesson {constructor() { this.hooks = { arch: new SyncBailHook(['name']) };}tap() { this.hooks.arch.tap('vue', function(name) { console.log('vue', name); return 'SyncBailHook当返回值是!undefined时,就会进行执行'; // ---------------------------- SyncBailHook当.tap()的参数监听函数返回值是 ( !undefined ) 时就不再往下继续执行 // return undefined ---------- 返回值是undefined则不受影响,因为函数默认的返回值就是undefined }); this.hooks.arch.tap('react', function(name) { console.log('react', name); });}start() { this.hooks.arch.call('woow_wu7');}}let lesson = new Lesson();lesson.tap();lesson.start();
SyncBailHook模仿实现
class SyncBailHook { constructor() { this.observers = [] // observers 是观察者对象组成的数组,观察者对象中蕴含event事件名称, 和fn工作函数 // [{event:eventName, fn: fn}] } // 注册观察者对象 // 更简略点能够间接注册事件 tap(eventName, fn) { this.observers.push({ event: eventName, fn }) } // 执行注册的观察者对象中的fn函数 call(...params) { // this.observers.forEach(item => item.fn(item.event, ...params)) let res = undefined; for(let i = 0; i < this.observers.length; i++) { const currentObj = this.observers[i] // ---------------------------------- 屡次用到,就缓存晋升性能 res = currentObj.fn(currentObj.event, ...params) if (res !== undefined) { // --------------------------- 循环数组时做判断,如果函数返回值是 (!undefined) 就跳出 .call() 函数 return } } }}const syncBailHook = new SyncBailHook(['params'])syncBailHook.tap('react', function(eventName, name) { console.log(eventName, name) return 'stop'})syncBailHook.tap('vue', function(eventName, name) { console.log(eventName, name)})syncBailHook.call('woow_wu7')
SyncWaterfallHook
let { SyncWaterfallHook } = require('tapable');// SyncWaterfallHook将上一个.tap() 的参数回调函数的 ( 返回值 ) 作为 ( 参数 ) 传给下一个 .tap() 的参数回调函数class Lesson { constructor() { this.hooks = { arch: new SyncWaterfallHook(['name']) }; } // 注册监听函数 tap() { this.hooks.arch.tap('vue', function(name) { console.log('vue', name); return 'vue不错'; // ---------------------------- SyncBailHook当返回值是 !undefined 时就不再往下继续执行 // return undefined ---------- 返回值是undefined则不受影响,因为函数默认的返回值就是undefined }); this.hooks.arch.tap('react', function(name) { console.log('react', name); // 这里的name就是上一个回调的返回值,即vue不错 return 'react不错' }); this.hooks.arch.tap('node', function(name) { console.log('node', name); // 这里的name是上一个回调的返回值,即react不错 }); } start() { this.hooks.arch.call('woow_wu7'); }}let lesson = new Lesson();lesson.tap();lesson.start();// vue woow_wu7// react vue不错// node react不错
SyncWaterfallHook模仿实现
次要利用数组的 reduce() 办法迭代
class SyncWaterfallHook {constructor() { this.observers = [] // observers 是观察者对象组成的数组,观察者对象中蕴含event事件名称, 和fn工作函数 // [{event:eventName, fn: fn}]}// 注册观察者对象// 更简略点能够间接注册事件tap(eventName, fn) { this.observers.push({ event: eventName, fn })}// 执行注册的观察者对象中的fn函数call(...params) { // this.observers.forEach(item => item.fn(item.event, ...params)) const [first, ...rest] = this.observers const fisrtRes = this.observers[0].fn(...params) rest.reduce((a, b) => { return b.fn(a) }, fisrtRes) // 第一次:当reduce存在第二个参数时,a = fisrtRes, b则就是数组的第一个成员 // 第二次:a = 第一次的返回值b.fn(a),b则是数组的第二个成员 // ...}}const syncWaterfallHook = new SyncWaterfallHook(['params'])syncWaterfallHook.tap('react', function(name) {console.log('react', name)return 'react ok'})syncWaterfallHook.tap('vue', function(name) {console.log('vue', name)return 'vue ok'})syncWaterfallHook.tap('node', function(name) {console.log('node', name)})syncWaterfallHook.call('woow_wu7')// react woow_wu7// vue react ok// node vue ok
SyncLoopHook
当 以后监听函数返回 ( !undefined ) 时会屡次执行,直到以后的监听函数返回undefined时进行执行以后函数,继续执行下一个函数
let { SyncLoopHook } = require('tapable');// SyncLoopHook // 当 .tap()的回调函数中返回值是 !undefined 时就会屡次执行,晓得返回值是undefined就会终止执行,则继续执行下一个 .tap()class Lesson {constructor() { this.hooks = { arch: new SyncLoopHook(['name']) };}index = 0;// 这里index是挂在Lesson.prototype上的// 如果在constructor中申明 this.index = 0 则 this示意实例对象// 之所以在tap()中能够调用this.index是因为tap的调用切实less实例上调用的,所以this示意实例则能够获取到this.index// 注册监听函数tap() { const that = this; this.hooks.arch.tap('react', function(name) { console.log('react', name); return ++that.index === 3 ? undefined : 'react不错'; // 返回值不为undefined就会始终执行该tap函数,直到为undefined }); this.hooks.arch.tap('node', function(name) { console.log('node', name); });}start() { this.hooks.arch.call('woow_wu7');}}let lesson = new Lesson();lesson.tap();lesson.start();
SyncLoopHook模仿实现
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <script>class SyncLoopHook { constructor() { this.observers = []; // observers 是观察者对象组成的数组,观察者对象中蕴含event事件名称, 和fn工作函数 // [{event:eventName, fn: fn}] } // 注册观察者对象 // 更简略点能够间接注册事件 tap(eventName, fn) { this.observers.push({ event: eventName, fn }) } // 执行注册的观察者对象中的fn函数 call(...params) { this.observers.forEach(item => { let res; do { res = item.fn(...params) } while(res !== undefined); }) }}const syncLoopHook = new SyncLoopHook(['params'])let count = 0;syncLoopHook.tap('react', function(name) { console.log('react', name) return ++count === 3 ? undefined : 'go on'})// 留神:函数的作用域是函数定义时所在的对象// 当在 .call() 办法中屡次调用时,count会减少,因为函数和变量的作用域都是在申明时确认的syncLoopHook.tap('vue', function(name) { console.log('vue', name)})syncLoopHook.tap('node', function(name) { console.log('node', name)})syncLoopHook.call('woow_wu7') </script></body></html>
AsyncParallelHook - 通过 .tapAsync(_, (name, cb)=>{异步代码})
和 .callAsync(_, ()=>{})
调用
- 异步并行的钩子
- <font color=red>parallel:是并行的意思</font>
<font color=red>series:是串行的意思</font>
let { AsyncParallelHook } = require('tapable');// AsyncParallelHook是异步并行的钩子// parallel:并行// series:串行class Lesson {constructor() { this.hooks = { arch: new AsyncParallelHook(['name']) };}// 注册监听函数tap() { this.hooks.arch.tapAsync('react', function (name, cb) { // ----------- tapAsync 异步注册 setTimeout(function () { console.log('react', name); cb(); // --------------------------------------------------------- cb()示意执行结束 }, 1000) }); this.hooks.arch.tapAsync('node', function (name, cb) { setTimeout(function () { console.log('node', name); cb(); // -----------------只有有一个cb()没有执行,在callAsync()中的回调函数就不会触发 }, 1000) });}start() { this.hooks.arch.callAsync('woow_wu7', function () { // --------------- callAsync异步执行 console.log('end') });}}let lesson = new Lesson();lesson.tap();lesson.start();
AsyncParallelHook模仿实现
class AsyncParallelHook { constructor() { this.observers = []; } tapAsync(eventName, fn) { this.observers.push({ event: eventName, fn }) } callAsync(...params) { const callbackFn = params.pop() // 取出callAsync()的最初一个参数,这里只有两个参数,即取出 cb 回调 // 留神:pop(), shift() 返回被删除的元素,扭转原数组 // 留神:push(), unshift() 返回操作后的数组长度,扭转原数组 const that = this // 固定thiss let count = 0 // 计数,用于统计 cb 回调执行的次数 function done() { count++; if (count === that.observers.length) { // 保障每一个 .tap() 中都执行了 cb() 回调 // 相等阐明每个 .tap()中都执行了 cb() // 阐明:此条件就是执行 callAsync()中的参数回调函数的条件 callbackFn() } } this.observers.forEach((item, index) => { item.fn(...params, done) // done函数作为参数传递给 .tapAsync()在外部被调用 }) }}const asyncParallelHook = new AsyncParallelHook(['params'])asyncParallelHook.tapAsync('react', function(name, cb) { setTimeout(() => { console.log('react', name) cb() }, 1000);})asyncParallelHook.tapAsync('vue', function(name, cb) { setTimeout(() => { console.log('vue', name) cb() }, 1000);})asyncParallelHook.tapAsync('node', function(name, cb) { setTimeout(() => { console.log('node', name) cb() }, 1000);})asyncParallelHook.callAsync('woow_wu7', function() { console.log('end')})
AsyncParallelHook - 通过 .tapPromise()
和 .promise().then()
调用
const { AsyncParallelHook } = require('tapable')class Lesson { constructor() { this.hook = { arch: new AsyncParallelHook(['name']) } } tap() { this.hook.arch.tapPromise('react', name => { return new Promise((resolove) => { setTimeout(() => { console.log('react', name) return resolove() }, 1000); }) }) this.hook.arch.tapPromise('vue', name => { return new Promise((resolove) => { setTimeout(() => { console.log('react', name) return resolove() }, 1000); }) }) } publish() { this.hook.arch.promise('woow_wu7').then(() => { console.log('end') }) }}const lesson = new Lesson(['name'])lesson.tap() // 注册lesson.publish() // 公布
AsyncParallelHook - 通过 .tapPromise()
和 .promise().then()
调用 - 模仿实现
class AsyncParallelHook { constructor() { this.observers = []; } tapPromise(eventName, fn) { this.observers.push({ event: eventName, fn }) } promise(...params) { const promiseArr = this.observers.map(item => item.fn(...params)) return Promise.all(promiseArr) // 返回一个Promise.all()函数 // 所有 resolve() 才 resolve() // 一个 rejectI() 就 reject() }}const asyncParallelHook = new AsyncParallelHook(['params'])asyncParallelHook.tapPromise('react', function (name) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name) return resolve() }, 1000); })})asyncParallelHook.tapPromise('vue', function (name) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name) return resolve() }, 1000); })})asyncParallelHook.promise('node').then(function (name) { console.log('end')})
AsyncSeriesHook - 异步串行钩子
在前一个.tap()执行完才会继续执行下一个.tap(),所有的.tap()中的cb()都执行完,才执行.callAsync()中的回调函数
const { AsyncSeriesHook } = require('tapable')class Lesson {constructor() { this.hook = { arch: new AsyncSeriesHook(['name']) }}tap() { this.hook.arch.tapAsync('react', (name, cb) => { setTimeout(() => { console.log('react', name) cb() }, 1000); }) this.hook.arch.tapAsync('vue', (name, cb) => { setTimeout(() => { console.log('vue', name) cb() }, 1000); })}publish() { this.hook.arch.callAsync('woow_wu7', () => { console.log('end') })}}const lesson = new Lesson(['name'])lesson.tap()lesson.publish()
AsyncSeriesHook模仿实现
class AsyncSeriesHook { constructor() { this.observers = [] } tapAsync(name, fn) { this.observers.push({ event_name: name, fn }) } callAsync(...params) { const lastCallback = params.pop() let index = 0; const that = this; function next() { if (index === that.observers.length) { return lastCallback() // 如果不等,就永远不会执行 lastCallback() } that.observers[index++].fn(...params, next) // index++ // 先整个赋值即 that.observes[0] // 再 index = index + 1 => index = 1 // 递归next()办法,直到 index === that.observers.length 后则返回 lastCallbackk() 进行递归 } next() }}const asyncSeriesHook = new AsyncSeriesHook(['name'])asyncSeriesHook.tapAsync('react', function(name, cb) { setTimeout(() => { console.log('react', name) cb() }, 1000);})asyncSeriesHook.tapAsync('vue', function(name, cb) { setTimeout(() => { console.log('vue', name) cb() }, 1000);})asyncSeriesHook.callAsync('woow_wu7', function() { console.log('end')})
AsyncSeriesWaterfullHook
const { AsyncSeriesWaterfallHook } = require('tapable')class Lesson { constructor() { this.hook = { arch: new AsyncSeriesWaterfallHook(['name']) } } tap() { this.hook.arch.tapAsync('react', (name, cb) => { setTimeout(() => { console.log('react', name) cb(null, 'next-react') // 当第一个参数布尔值是false时, 将传递 'next-react' 作为下一个 .tapAsynce() 中参数回调函数的参数 // 当第一个参数布尔值是true时,将中断下一个 .tapAsynce() 的执行 // 留神: 尽管会中断下一个.tapAsynce() // 然而因为调用了cb()且为.tapAsynce()的总个数时,所以callAsync的第二个参数回调会执行,即会打印'end' }, 1000); }) this.hook.arch.tapAsync('vue', (name, cb) => { setTimeout(() => { console.log('vue', name) cb(null, 'next-vue') // 如果是cb('null', 'next-vue') 第一个参数布尔值是true,则不会执行下一个.tapAsynce() }, 1000); }) } publish() { this.hook.arch.callAsync('woow_wu7', () => { console.log('end') }) }}const lesson = new Lesson(['name'])lesson.tap()lesson.publish()
AsyncSeriesWaterfullHook模仿实现
class AsyncSeriesWaterfallHook { constructor() { this.observers = [] } tapAsync(name, fn) { this.observers.push({ event_name: name, fn }) } callAsync(...params) { const lastCallback = params.pop() let index = 0; const that = this; function next(err, data) { let task = that.observers[index] if (!task || err || index === that.observers.length) { // 如果该task不存在 // 或者 err存在 // 或者 index达到最大长度,其实能够不判断,因为index超出that.observers.length时,task将不存在了,满足第一个条件 // 满足以上条件,都间接返回lastCallback return lastCallback() } if (index === 0) { task.fn(...params, next) } else { task.fn(data, next) } index++ } next() }}const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(['name'])asyncSeriesWaterfallHook.tapAsync('react', function(name, cb) { setTimeout(() => { console.log('react', name) cb(null, 'next-react') }, 1000);})asyncSeriesWaterfallHook.tapAsync('vue', function(name, cb) { setTimeout(() => { console.log('vue', name) cb(null, 'next-vue') }, 1000);})asyncSeriesWaterfallHook.callAsync('woow_wu7', function() { console.log('end')})
材料
tapable https://github.com/webpack/ta...
tapable https://juejin.im/post/684490...
tapable https://juejin.im/post/684490...