Hook 這個詞不少人都聽過,就算不知道,也直接或間接地用到過。它一般是系統或框架開放出來供上層應用開發者執行代碼的一種方式。例如,Vue 的生命週期鉤子,本質就是框架內部在對應時機調用了組件定義的鉤子函數;此外,Webpack 所使用 tapable
更是將 hook
的應用發揮的淋漓盡致,tapable
最值得稱讚的就是,它對 hook
作了很好的抽象和分類。數組
對於開發者,Hook 一般以鉤子函數形式存在。開發者註冊鉤子函數,系統或者框架決定在何時調用鉤子函數,某種意義上,它和事件回調函數有殊途同歸之妙。從數據結構的設計上,咱們能夠使用鍵值對(散列表,JS中的普通對象)來表示系統提供的鉤子,其中,鍵表明鉤子名稱,值是鉤子函數數組。promise
例如,下面的代碼包含一個init
鉤子,這個鉤子註冊了3個鉤子函數:fn1, fn2, fn3
。markdown
const hooks = { init: [fn1, fn2, fn3] } 複製代碼
若是咱們將 Hook 看做是一種和棧
,隊列
同樣的抽象數據類型(ADT),那麼 Hook 的操做集合包含註冊(Register)
和 調用(Call)
。簡單實現就是:數據結構
// 註冊鉤子 function regHook(hookName, hookFn) { if (!hooks[hookName]) hooks[hookName] = [] hooks[hookName].push(hookFn) } // 調用鉤子 function callHook(hookName, ...args) { hooks[hookName].forEach(fn => fn(...args)) } 複製代碼
根據鉤子函數的執行順序,能夠分爲:併發
根據鉤子函數的執行方式,能夠分爲:框架
promise.then
來獲取鉤子執行結束的消息註冊鉤子比較簡單,只需將鉤子函數按順序加入鉤子函數數組便可。而調用鉤子,須要根據鉤子類型來採起不一樣調用方法。異步
同步鉤子的調用是最簡單,按順序調用一遍鉤子函數便可,而且只有串行執行。async
function callSync(hookName, ...args) { hooks[hookName].forEach(fn => fn(...args)) } 複製代碼
異步鉤子的調用要分爲串行和並行。函數
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() } 複製代碼
使用示例:this
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 複製代碼
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() }) } 複製代碼
使用示例:
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 複製代碼
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) } } 複製代碼
使用示例:
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') 複製代碼
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) } }) } 複製代碼
使用示例:
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') }) 複製代碼
class Hook { constructor() { this.hookFns = [] } reg(fn) { this.hookFns.push(fn) } call(...args) { this.hookFns.forEach(fn => fn(...args)) } } 複製代碼
class AsyncHook extends Hook { call(...args, done) { const fns = this.hookFns let i = fns.length let next = done while(i) { let fn = fns[--i] let _next = next next = () => fn(...args, _next) } next() } callParallel(...args, done) { const fns = this.hookFns let count = fns.length let _done = () => { count-- if (count === 0) { done() } } fns.forEach(fn => fn(...args, _done)) } callParallelN(...args, done) { 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++](...args, _done) } } for (; cur < limit; cur++) { fns[cur](...args, _done) } } } 複製代碼
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(...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) } }) } } 複製代碼