[深刻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] Hookshtml
[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI前端
[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程vue
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:瀑布
複製代碼
// 發佈訂閱模式
<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>
複製代碼
<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>
複製代碼
npm install tapable -S
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() // 發佈
複製代碼
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')
複製代碼
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();
複製代碼
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')
複製代碼
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不錯
複製代碼
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
複製代碼
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();
複製代碼
<!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>
複製代碼
.tapAsync(_, (name, cb)=>{異步代碼})
和 .callAsync(_, ()=>{})
調用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();
複製代碼
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')
})
複製代碼
.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() // 發佈
複製代碼
.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')
})
複製代碼
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()
複製代碼
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')
})
複製代碼
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()
複製代碼
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…node