原文:https://juejin.cn/post/7124163407577612302
願意花更多的時間、傾注更多的精力來跟大家一起努力。
目錄
apply
async await
bind
call
concurrent-request
debounce
deep-copy
event-bus
繼承
instanceof
new
object-create
promise
throttle
參與
apply
爲函數綁定執行上下文
原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文的方法
tx 指定的函數執行上下文
args 剩餘參數組成的數組
any 返回函數的執行結果
// 爲函數綁定執行上下文// 原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文的方法// ctx 指定的函數執行上下文// args 剩餘參數組成的數組// any 返回函數的執行結果Function.prototype.myApply = function (ctx, args) { // fn.myApply(ctx, [arg1, arg2])
// this是正在執行的函數
const fn = this
// 保證 ctx[key] 的唯一性,避免和用戶設置的 context[key] 衝突
const key = Symbol() // 將執行函數設置到指定的上下文對象上
ctx[key] = fn // 執行函數
const res = ctx[key](...args) // 刪除上下文上的 fn 方法
delete ctx[key] // 返回函數的執行結果
return res
}複製代碼
async await
async await 是 Generator 的語法糖,其本質是 Generator + 自動執行器
Generator 函數
執行 generator 函數,拿到 yield 表達式的執行結果 => { next: () => void }
自動執行器
{ value: any, done: boolean }
說明 yield 後面跟的是 Promise 實例
// async await 是 Generator 的語法糖,其本質是 Generator + 自動執行器// Generator 函數module.exports = function asyncAwait(generatorFn) { // 執行 generator 函數,拿到 yield 表達式的執行結果 => { next: () => void }
const yieldExpRet = generatorFn() // 自動執行器
function autoActuator() { // { value: any, done: boolean }
const ret = yieldExpRet.next() if (!ret.done) { if (object.prototype.toString.call(ret?.value?.then) === '[object Function]') { // 說明 yield 後面跟的是 Promise 實例
ret.value.then(() => { autoActuator()
})
} else { // 同步
autoActuator()
}
}
} autoActuator()
}複製代碼
bind
爲函數綁定執行上下文
原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文上的方法
ctx 指定的函數執行上下文
args 剩餘參數組成的數組
fn.myBind(ctx, [arg1, arg2])
this是正在執行的函數
保證 ctx[key] 的唯一性,避免和用戶設置的 context[key] 衝突
將執行函數設置到指定的上下文對象上
返回一個可執行函數
bind 方法支持預設一部分參數,剩下的參數通過返回的函數設置,具有柯里化的作用
執行函數
// 爲函數綁定執行上下文// 原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文上的方法// ctx 指定的函數執行上下文// args 剩餘參數組成的數組Function.prototype.myBind = function (ctx, ...args) { // fn.myBind(ctx, [arg1, arg2])
// this是正在執行的函數
const fn = this
// 保證 ctx[key] 的唯一性,避免和用戶設置的 context[key] 衝突
const key = Symbol() // 將執行函數設置到指定的上下文對象上
ctx[key] = fn // 返回一個可執行函數
// bind 方法支持預設一部分參數,剩下的參數通過返回的函數設置,具有柯里化的作用
return function(...otherArgs) { // 執行函數
return ctx[key](...args, ...otherArgs)
}
}複製代碼
call
爲函數綁定指定上下文
原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文上的方法
ctx 指定的函數執行上下文
args 剩餘參數組成的數組
any 返回函數的執行結果
fn.myCall(ctx, arg1, arg2)
this是正在執行的函數
保證 ctx[key] 的唯一性,避免和用戶設置的 context[key] 衝突
將執行函數設置到指定的上下文對象上
執行函數
刪除上下文上的fn方法
返回函數的執行結果
// 爲函數綁定指定上下文// 原理:將函數設置爲執行上下文的一個方法,然後調用執行上下文上的方法// ctx 指定的函數執行上下文// args 剩餘參數組成的數組// any 返回函數的執行結果Function.prototype.myCall = function (ctx, ...args) { // fn.myCall(ctx, arg1, arg2)
// this是正在執行的函數
const fn = this
// 保證 ctx[key] 的唯一性,避免和用戶設置的 context[key] 衝突
const key = Symbol() // 將執行函數設置到指定的上下文對象上
ctx[key] = fn // 執行函數
const res = ctx[key](...args) // 刪除上下文上的fn方法
delete ctx[key] // 返回函數的執行結果
return res
}複製代碼
concurrent-request
併發請求,控制請求併發數
taskQueues 一個個請求任務組成的數組
concurrentNum 請求的併發數
存放所有任務的執行結果
開始先發送指定數量的併發請求
當每個請求完成後再遞歸的調用自身,發送任務隊列的下一個請求
遞歸終止條件(任務隊列爲空)
從任務隊列中彈出一個任務
執行任務
當任務完成後遞歸調用 req, 發送隊列中的下一個請求
並將任務結果 push 進結果數組中
// 併發請求,控制請求併發數// taskQueues 一個個請求任務組成的數組// concurrentNum 請求的併發數module.exports = function concurrentRequest(taskQueues = [], concurrentNum = 1) { return new Promise(resolve => { // 存放所有任務的執行結果
const taskRet = [] // 開始先發送指定數量的併發請求
while (concurrentNum > 0) { req()
concurrentNum--
} // 當每個請求完成後再遞歸的調用自身,發送任務隊列的下一個請求
function req() { // 遞歸終止條件(任務隊列爲空)
if (!taskQueues.length) return Promise.allSettled(taskRet).then(res => { resolve(res)
}) // 從任務隊列中彈出一個任務
const task = taskQueues.shift() // 執行任務
const ret = task() // 當任務完成後遞歸調用 req, 發送隊列中的下一個請求
res.then(() => { req()
}) // 並將任務結果 push 進結果數組中
taskRet.push(ret)
}
})
}複製代碼
debounce
防抖
原理:事件被觸發 wait 毫秒後執行回調fn, 如果在wait期間再次觸發事件,則重新計時
fn 事件觸發後的回調函數
wait 延遲時間,wait 毫秒後執行fn
返回經過包裝後的事件處理函數
定時器,這裏用到了閉包
返回經過包裝後的事件處理函數
如果 timer 爲不爲空,則說明在 wait 時間內已經觸發過該事件了,而且事件處理函數仍未被調用
說明在wait事件內事件被重複觸發了,則需要進行防抖處理,即清除之前的定時器,這樣上一次事件觸發後的回調就不會被執行
定時器也會重新設置
通過定時器來實現事件觸發後在 wait 毫秒後執行事件處理函數
需要給回調綁定上下文this,即觸發事件的目標對象
// 防抖// 原理:事件被觸發 wait 毫秒後執行回調fn, 如果在wait期間再次觸發事件,則重新計時// fn 事件觸發後的回調函數// wait 延遲時間,wait 毫秒後執行fn// 返回經過包裝後的事件處理函數function debounce(fn, wait = 50) { // 定時器,這裏用到了閉包
let timer = null
// 返回經過包裝後的事件處理函數
return function(...args) { // 如果 timer 爲不爲空,則說明在 wait 時間內已經觸發過該事件了,而且事件處理函數仍未被調用
// 說明在wait事件內事件被重複觸發了,則需要進行防抖處理,即清除之前的定時器,這樣上一次事件觸發後的回調就不會被執行
// 定時器也會重新設置
if (timer) { clearTimeout(timer)
} // 通過定時器來實現事件觸發後在 wait 毫秒後執行事件處理函數
timer = setTimeout(() => { // 需要給回調綁定上下文this,即觸發事件的目標對象
fn.apply(this, args)
timer = null
}, wait)
}
}複製代碼
deep-copy
深拷貝
src 原數據
返回拷貝後的數據
拷貝原始值,直接返回原始值本身
解決循環引用的問題
拷貝數組
拷貝 Map 對象
拷貝函數
拷貝對象
判斷數據是否爲原始值類型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)
Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...
// 深拷貝// src 原數據// 返回拷貝後的數據module.exports = function deepCopy(src, cache = new WeakMap()) { // 拷貝原始值,直接返回原始值本身
if (isPrimitiveType(src)) return src // 解決循環引用的問題
if (cache.has(src)) return src
cache.set(src, true) // 拷貝數組
if (isArray(src)) { const ret = [] for (let i = 0, len = src.length; i < len; i++) {
ret.push(deepCopy(src[i], cache))
} return ret
} // 拷貝 Map 對象
if (isMap(src)) { const ret = new Map()
src.forEach((value, key) => {
ret.set(key, deepCopy(value, cache))
}) return ret
} // 拷貝函數
if (isFunction(src)) { copyFunction(src)
} // 拷貝對象
if (isObject(src)) { // 獲取對象上的所有key
const keys = [...Object.keys(src), ...Object.getOwnPropertySymbols(src)] const ret = {} // 遍歷所有的key,遞歸調用 deepCopy 拷貝 obj[key] 的值
keys.forEach(item => {
ret[item] = deepCopy(src[item], cache)
}) // 返回拷貝後的對象
return ret
}
}// 判斷數據是否爲原始值類型(Number, Boolean,String,Symbol ,BigInt ,Null ,Undefined)function isPrimitiveType(data) { const primitiveType = ['Number', 'Boolean', 'String', 'Symbol', 'BigInt', 'Null', 'Undefined'] return primitiveType.includes(getDataType(data))
}// 判斷數據是否爲Object類型function isObject(data) { return getDataType(data) === 'Object'}// 判斷數據是否爲函數function isFunction(data) { return getDataType(data) === 'Function'}// 判斷數據是否爲數組function isArray(data) { return getDataType(data) === 'Array'}// 判斷數據是否爲Mapfunction isMap(data) { return getDataType(data) === 'Map'}// 獲取數據類型// Number,Boolean,String,Symbol,BigInt,Null,Undefined,Object,Array,Function,Date...function getDataType(data) { return Object.prototype.toString.apply(data).slice(8, -1)
}// 拷貝函數function copyFunction(src) { const fnName = src.name
let srcStr = src.toString() // 匹配function fnName, 比如 function fnName() {}
const fnDecExp = new RegExp(`function (${fnName})?`) // 切除匹配內容,srcStr = (xxx) {} 或 (xxx) => {}
srcStr = srcStr.replace(fnDecExp, '') // 匹配函數參數
const argsExg = /\((.*)\)/
let args = argsExg.exec(srcStr) // 函數體
const fnBody = srcStr.replace(argsExg, '').trim() // { return 'test' } => return 'test'
const fnBodyCode = /^{(.*)}$/.exec(fnBody) // 得到了函數的名稱,參數,函數體,重新聲明函數
return new Function(...args[1].split(','), fnBodyCode[1])
}複製代碼
event-bus
Event bus
發佈訂閱設計模式的應用,node.js 的基礎模塊,也是前端組件通信的一種手段,比如Vue的on和on和emit
以事件名爲key,事件處理函數組成的數組爲value
監聽事件
eventName 事件名
cb 事件處理函數
// Event bus// 發佈訂閱設計模式的應用,node.js 的基礎模塊,也是前端組件通信的一種手段,比如Vue的$on和$emitfunction EventBus() { // 以事件名爲key,事件處理函數組成的數組爲value
this.events = {}
}module.exports = EventBus// 監聽事件// eventName 事件名// cb 事件處理函數EventBus.prototype.$on = function(eventName, cb) { if (!Array.isArray(cb)) {
cb = [cb]
} this.events[eventName] = (this.events[eventName] || []).concat(cb)
}EventBus.prototype.$emit = function(eventName, ...args) { this.events[eventName].foEach(fn => {
fn.apply(this, args)
})
}複製代碼
繼承
JavaScript 的繼承方式有很多,比如簡單的基於 Object.create 實現的繼承,每種方式或多或少都有些缺陷
這種缺陷是語言層面導致的,避免不了,即使是 class 語法(糖)。
組合式繼承,class 語法糖的本質
在this上繼承父類的屬性
繼承父類的方法
恢復子類的構造函數,上面一行會將 Child.prototype.constructor 改爲 Parent.prototype.constructor
// JavaScript 的繼承方式有很多,比如簡單的基於 Object.create 實現的繼承,每種方式或多或少都有些缺陷,// 這種缺陷是語言層面導致的,避免不了,即使是 class 語法(糖)。// 組合式繼承,class 語法糖的本質function Parent(...args) { this.name = 'Parent name'
this.args = args
}Parent.prototype.parentFn = function() { console.log('name = ', this.name) console.log('args = ', this.args)
}function Chid(args1,args2) { // 在this上繼承父類的屬性
Parent.call(this, args1, args2) this.childName = 'child name'}// 繼承父類的方法Child.prototype = Object.create(Parent.prototype)// 恢復子類的構造函數,上面一行會將 Child.prototype.constructor 改爲 Parent.prototype.constructorChild.prototype.constructor = Childmodule.exports = Child複製代碼
instanceof
instanceof運算符
定義:判斷對象是否屬於某個構造函數的實例
原理:判斷構造函數的原型對象是否出現在對象的原型鏈上
// instanceof運算符// 定義:判斷對象是否屬於某個構造函數的實例// 原理:判斷構造函數的原型對象是否出現在對象的原型鏈上module.exports = function customINstanceof (ins, constructor) { const proto = Object.getPrototypeOf(ins) if (proto === constructor.prototype) return true
if (!proto) return false
return customINstanceof(proto, constructor)
}複製代碼
new
new 運算符
作用:負責實例化一個類(構造函數)
1.創建一個構造函數原型對象爲原型的對象
2.以第一步的對象爲上下文執行構造函數
3.返回值,如果函數有返回值,則返回函數的返回值,否則返回第一步創建的對象。
// new 運算符// 作用:負責實例化一個類(構造函數)// 原理:// 1.創建一個構造函數原型對象爲原型的對象// 2.以第一步的對象爲上下文執行構造函數// 3.返回值,如果函數有返回值,則返回函數的返回值,否則返回第一步創建的對象。// Function 構造函數// Array 構造函數的其他參數組成的數組// 對象實例module.exports = function newOperator(constructor, ...args) { const ins = Object.create(constructor.prototype) const res = constructor.apply(ins, args) return res || ins
}複製代碼
object-create
proto 新對象的原型對象
props Object.defineProperties 的第二個參數,要定義其可枚舉屬性或修改的屬性描述符的對象。對象中存在的屬性描述符:數據描述符和訪問器描述符
// Object.create// proto 新對象的原型對象// props Object.defineProperties 的第二個參數,要定義其可枚舉屬性或修改的屬性描述符的對象。對象中存在的屬性描述符:數據描述符和訪問器描述符Object.myCreate = function(proto, props) { if (typeof proto !== 'object') { console.error('Object prototype may only be an Object or null') return
} // 創建的空對象
const obj = {} // 設置原型對象
Object.setPrototypeOf(obj, proto) // 設置對象的初始數據
if (props) { Object.defineProperties(obj, props)
} return obj
}複製代碼
promise
Promise,解決了回調地獄的問題
executor 同步執行
promise 狀態不可逆
then 回調必須在 promise 狀態改變後執行
promise 鏈式調用,後一個回調的參數是前一個回調的返回值
實例化 Promise 時 executor 被同步執行
// Promise,解決了回調地獄的問題// executor 同步執行// promise 狀態不可逆// then 回調必須在 promise 狀態改變後執行// promise 鏈式調用,後一個回調的參數是前一個回調的返回值// 實例化 Promise 時 executor 被同步執行function MyPromise(executor) { // 緩存this實例
const _self = this
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.fulfilledCb = () => {} this.rejectedCb = () => {} function resolve(value) { setTimeout(() => { // 狀態不可逆
if (_self.status === 'pending') {
_self.status = 'fulfilled'
_self.value = value
_self.fulfilledCb(value)
}
})
} function reject(errMsg) { setTimeout(() => { // 狀態不可逆
if (_self.status === 'pending') {
_self.status = 'rejected'
_self.reason = errMsg
_self.rejectedCb(errMsg)
}
})
} try { executor(resolve, reject)
} catch (err) { reject(err)
}
}MyPromise.prototype.then = function(fulfilledCb, rejectedCb) { const _self = this
return new MyPromise((resolve, reject) => {
_self.fulfilledCb = function (value) { resolve(fulfilledCb(value))
}
_self.rejectedCb = function (reason) { reject(rejectedCb(reason))
}
})
}MyPromise.race = function (promiseArr) { return new MyPromise((resolve, reject) => { for (let i = 0, len = promiseArr.length; i < len; i++) { const p = promiseArr[i]
p.then(resolve, reject)
}
})
}MyPromise.all = function (promiseArr) { return new MyPromise((resolve, reject) => { const len = promiseArr.length
const result = [] for (let i = 0; i < len; i++) { const p = promiseArr[i]
p.then((res) => {
result.push(res) if (result.length === len) { resolve(result)
}
}, (errMsg) => { reject(errMsg)
})
}
})
}module.exports = MyPromise複製代碼
throttle
節流
原理:事件被頻繁觸發時,事件回調函數會按照固定頻率執行,比如1s 執行一次,只有上個事件回調被執行之後下一個事件回調纔會執行
事件回調函數
wait 事件回調的執行頻率,每wait毫秒執行一次
// 節流// 原理:事件被頻繁觸發時,事件回調函數會按照固定頻率執行,比如1s 執行一次,只有上個事件回調被執行之後下一個事件回調纔會執行// 事件回調函數// wait 事件回調的執行頻率,每wait毫秒執行一次function throttle(fn, wait = 500) { let timer = null
return function(...args) { if (timer) return
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, wait)
}
}複製代碼
go!go!go!
本文同步分享在 博客“掘金-我是哪吒”(CSDN)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。