導航
[深入01] 執行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件迴圈
[深入05] 柯里化 偏函式 函式記憶
[深入06] 隱式轉換 和 運算子
[深入07] 瀏覽器快取機制(http快取機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模組化
[深入13] 觀察者模式 釋出訂閱模式 雙向資料繫結
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[react] Hooks
[部署01] Nginx
[部署02] Docker 部署vue專案
[部署03] gitlab-CI
[原始碼-webpack01-前置知識] AST抽象語法樹
[原始碼-webpack02-前置知識] Tapable
前置知識
一些單詞
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
- ( 中介 ) 既要接收 ( 釋出者 ) 釋出的訊息,又要將訊息派發給訂閱了該事件的 ( 訂閱者 )
- ( 中介 ) 需要根據 ( 不同的事件 ),儲存相應的 ( 訂閱者 ) 資訊
- 通過 ( 中介物件 ) 完全 ( 解耦 ) 了 ( 釋出者 ) 和 ( 訂閱者 )
- 釋出訂閱模式程式碼實現 - 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() 中的監聽函式返回值是 ( !undefined ) 時會 ( 終止執行 ) .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(_, ()=>{})
呼叫
- 非同步並行的鉤子
- parallel:是並行的意思
- series:是序列的意思
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 github.com/webpack/tap…
tapable juejin.im/post/5c5d96…
tapable juejin.im/post/5c25f9…