前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS 中的钩子(Hook)实现

JS 中的钩子(Hook)实现

作者头像
我是一条小青蛇
发布2020-12-09 10:11:40
2.8K0
发布2020-12-09 10:11:40
举报
文章被收录于专栏:青笔原创青笔原创

1. Hook 简介

Hook 这个词很多人都听过,就算不知道,也直接或间接地用到过。它通常是系统或框架开放出来供上层应用开发者执行代码的一种方式。例如,Vue 的生命周期钩子,本质就是框架内部在对应时机调用了组件定义的钩子函数;此外,Webpack 所使用 tapable 更是将 hook 的应用发挥的淋漓尽致,tapable 最值得称赞的就是,它对 hook 做了很好的抽象和分类。

2. Hook 的表现形式

对于开发者,Hook 通常以钩子函数形式存在。开发者注册钩子函数,系统或者框架决定在什么时候调用钩子函数,某种意义上,它和事件回调函数有异曲同工之妙。从数据结构的设计上,我们可以使用键值对(散列表,JS中的普通对象)来表示系统提供的钩子,其中,键代表钩子名称,值是钩子函数数组。

例如,下面的代码包含一个init钩子,这个钩子注册了3个钩子函数:fn1, fn2, fn3

代码语言:javascript
复制
const hooks = {
  init: [fn1, fn2, fn3]
}

如果我们将 Hook 看作是一种和队列一样的抽象数据类型(ADT),那么 Hook 的操作集合包含注册(Register)调用(Call)。简单实现就是:

代码语言:javascript
复制
// 注册钩子
function regHook(hookName, hookFn) {
  if (!hooks[hookName]) hooks[hookName] = []
  hooks[hookName].push(hookFn)
}
// 调用钩子
function callHook(hookName, ...args) {
  hooks[hookName].forEach(fn => fn(...args))
}

3. Hook 的分类

3.1 串行和并行

根据钩子函数的执行顺序,可以分为:

  • 串行钩子:根据注册顺序调用钩子,后面的钩子必须等到前面的钩子执行完后才能调用,串行钩子可以是同步的,也可以是异步的
  • 并行钩子:按顺序调用钩子,但可同时执行,即后面的钩子不用等到前面的钩子执行完成,显然,并行钩子必须是异步的

3.2 同步和异步

根据钩子函数的执行方式,可以分为:

  • 同步钩子: 钩子执行会阻塞主线程,钩子函数返回即代表钩子执行结束
  • 异步钩子: 钩子执行不会阻塞主线程,钩子函数返回不代表钩子执行结束,需要使用回调函数或者使用promise.then 来获取钩子执行结束的消息

4. Hook 调用

注册钩子比较简单,只需将钩子函数按顺序加入钩子函数数组即可。而调用钩子,需要根据钩子类型来采取不同调用方法。

4.1 同步钩子的调用

同步钩子的调用是最简单,按顺序调用一遍钩子函数即可,并且只有串行执行。

代码语言:javascript
复制
function callSync(hookName, ...args) {
  hooks[hookName].forEach(fn => fn(...args))
}

4.2 异步钩子的调用

异步钩子的调用要分为串行和并行。

4.2 异步串行
4.2.1 基于回调函数
代码语言:javascript
复制
function callAsyncSeries(hookName, done, ...args) {
  const fns = hooks[hookName]
  let i = fns.length
  let next = done
  while(i) {
    let fn = fns[--i]
    let _next = next
    next = () => fn(...args, _next)
  }
  next()
}

使用示例:

代码语言:javascript
复制
regHook('asyncSer', (a, b, done) => { setTimeout(() => { console.log('timout 1', a, b); done() }, 1000) })
regHook('asyncSer', (a, b, done) => { setTimeout(() => { console.log('timout 2', a, b); done() }, 2000) })
regHook('asyncSer', (a, b, done) => { setTimeout(() => { console.log('timout 3', a, b); done() }, 3000) })

callAsyncSeries('asyncSer', () => { console.log('done') }, 'aa', 'bb')

// timout 1 aa bb
// timout 2 aa bb
// timout 3 aa bb
// done
4.2.2 基于 Promise
代码语言:javascript
复制
function callPromiseSeries(hookName, ...args) {
  return new Promise(resolve => {
    const fns = hooks[hookName]
    let i = fns.length
    let next = resolve
    while(i) {
      let fn = fns[--i]
      let _next = next
      next = () => fn(...args).then(_next)
    }
    next()
  })
}

使用示例:

代码语言:javascript
复制
regHook('promiseSer', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseSer 1', a, b); resolve() }, 2000)
  )
})

regHook('promiseSer', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseSer 2', a, b); resolve() }, 3000)
  )
})

regHook('promiseSer', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseSer 3', a, b); resolve() }, 1000)
  )
})

callPromiseSeries('promiseSer', 'aa', 'bb').then(() => { console.log('done') })

// promiseSer 1 aa bb
// promiseSer 2 aa bb
// promiseSer 3 aa bb
// done

4.3 异步并行钩子的调用

4.3.1 基于回调函数
代码语言:javascript
复制
function callAsyncParallel(hookName, done, ...args) {
  const fns = hooks[hookName]
  let count = fns.length
  let _done = () => {
    count--
    if (count === 0) {
      done()
    }
  }
  fns.forEach(fn => fn(...args, _done))
}

// 限制并发数
function callAsyncParallelN(hookName, done, N, ...args) {
  const fns = hooks[hookName]
  let count = fns.length
  let cur = 0
  let limit = N < fns.length ? N : fns.length
  let _done = () => {
    count--
    if (count === 0) {
      done()
    } else if (cur < fns.length) {
      fns[cur++](...args, _done)
    }
  }
  for (; cur < limit; cur++) {
    fns[cur](...args, _done)
  }
}

使用示例:

代码语言:javascript
复制
regHook('asyncParallel', (a, b, done) => { setTimeout(() => { console.log('asyncParallel 1', a, b); done() }, 1000) })
regHook('asyncParallel', (a, b, done) => { setTimeout(() => { console.log('asyncParallel 2', a, b); done() }, 1000) })
regHook('asyncParallel', (a, b, done) => { setTimeout(() => { console.log('asyncParallel 3', a, b); done() }, 1000) })

callAsyncParallel('asyncParallel', () => { console.log('done') }, 'aa', 'bb')
callAsyncParallelN('asyncParallel', () => { console.log('done') }, 2, 'aa', 'bb')
4.3.2 基于 Promise
代码语言:javascript
复制
function callPromiseParallel(hookName, ...args) {
  return new Promise(resolve => {
    const fns = hooks[hookName]
    let count = fns.length
    let _done = () => {
      count--
      if (count === 0) {
        resolve()
      }
    }
    fns.forEach(fn => fn(...args).then(_done))
  })
}

// 限制并发数
function callPromiseParallelN(hookName, N, ...args) {
  return new Promise(resolve => {
    const fns = hooks[hookName]
    let count = fns.length
    let cur = 0
    let limit = N < fns.length ? N : fns.length
    let _done = () => {
      count--
      if (count === 0) {
        resolve()
      } else {
        if (cur < fns.length) {
          fns[cur++](...args).then(_done)
        }
      }
    }
    for (; cur < limit; cur++) {
      fns[cur](...args).then(_done)
    }
  })
}

使用示例:

代码语言:javascript
复制
regHook('promiseParallel', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseParallel 1', a, b); resolve() }, 1000)
  )
})

regHook('promiseParallel', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseParallel 2', a, b); resolve() }, 1000)
  )
})

regHook('promiseParallel', (a, b) => {
  return new Promise(resolve => 
    setTimeout(() => { console.log('promiseParallel 3', a, b); resolve() }, 1000)
  )
})

callPromiseParallel('promiseParallel', 'aa', 'bb').then(() => { console.log('done') })
callPromiseParallelN('promiseParallel', 2, 'aa', 'bb').then(() => { console.log('done') })

5. 代码封装

5.1 同步钩子

代码语言:javascript
复制
class Hook {
  constructor() {
    this.hookFns = []
  }
  reg(fn) {
    this.hookFns.push(fn)
  }
  call(...args) {
    this.hookFns.forEach(fn => fn(...args))
  }
}

5.2 异步回调钩子

代码语言:javascript
复制
class AsyncHook extends Hook {
  call(done, ...args) {
    const fns = this.hookFns
    let i = fns.length
    let next = done
    while(i) {
      let fn = fns[--i]
      let _next = next
      next = () => fn(_next, ...args)
    }
    next()
  }
  callParallel(done, ...args) {
    const fns = this.hookFns
    let count = fns.length
    let _done = () => {
      count--
      if (count === 0) {
        done()
      }
    }
    fns.forEach(fn => fn(_done, ...args))
  }
  callParallelN(N, done, ...args) {
    const fns = this.hookFns
    let count = fns.length
    let cur = 0
    let limit = N < fns.length ? N : fns.length
    let _done = () => {
      count--
      if (count === 0) {
        done()
      } else if (cur < fns.length) {
        fns[cur++](_done, ...args)
      }
    }
    for (; cur < limit; cur++) {
      fns[cur](_done, ...args)
    }
  }
}

5.3 异步 Promise 钩子

代码语言:javascript
复制
class PromiseHook extends Hook {
  call(...args) {
    return new Promise(resolve => {
      const fns = this.hookFns
      let i = fns.length
      let next = resolve
      while(i) {
        let fn = fns[--i]
        let _next = next
        next = () => fn(...args).then(_next)
      }
      next()
    })
  }
  callParallel(...args) {
    return new Promise(resolve => {
      const fns = this.hookFns
      let count = fns.length
      let _done = () => {
        count--
        if (count === 0) {
          resolve()
        }
      }
      fns.forEach(fn => fn(...args).then(_done))
    })
  }
  callParallelN(N, ...args) {
    return new Promise(resolve => {
      const fns = this.hookFns
      let count = fns.length
      let cur = 0
      let limit = N < fns.length ? N : fns.length
      let _done = () => {
        count--
        if (count === 0) {
          resolve()
        } else {
          if (cur < fns.length) {
            fns[cur++](...args).then(_done)
          }
        }
      }
      for (; cur < limit; cur++) {
        fns[cur](...args).then(_done)
      }
    })
  }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-192,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Hook 简介
  • 2. Hook 的表现形式
  • 3. Hook 的分类
    • 3.1 串行和并行
      • 3.2 同步和异步
      • 4. Hook 调用
        • 4.1 同步钩子的调用
          • 4.2 异步钩子的调用
            • 4.2 异步串行
          • 4.3 异步并行钩子的调用
          • 5. 代码封装
            • 5.1 同步钩子
              • 5.2 异步回调钩子
                • 5.3 异步 Promise 钩子
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档