导航

[[深刻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...