模拟call
- 第一個參數為
或者null
時,undefined
指向全局對象this
,值為原始值的指向該原始值的自動包裝對象,如 window
、String
、Number
Boolean
- 為了避免函數名與上下文(
)的屬性發生沖突,使用context
類型作為唯一值Symbol
- 将函數作為傳入的上下文(
)屬性執行context
- 函數執行完成後删除該屬性
- 傳回執行結果
Function.prototype.myCall = function(context, ...args) {
context = (context ?? window) || new Object(context)
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
注: 代碼實作使用了
ES2020
新特性
Null
判斷符
??
, 詳細參考阮一峰老師的ECMAScript 6 入門
模拟apply
- 前部分與
一樣call
- 第二個參數可以不傳,但類型必須為數組或者類數組
Function.prototype.myApply = function(context) {
context = (context ?? window) || new Object(context)
const key = Symbol()
const args = arguments[1]
context[key] = this
let result
if(args) {
result = context[key](...args)
} else {
result = context[key]
}
delete context[key]
return result
}
注:代碼實作存在缺陷,當第二個參數為類數組時,未作判斷(有興趣可查閱一下如何判斷類數組)
模拟bind
- 使用
指定 call / apply
this
- 傳回一個綁定函數
- 當傳回的綁定函數作為構造函數被
調用,綁定的上下文指向執行個體對象new
- 設定綁定函數的
為原函數的prototype
prototype
Function.prototype.myBind = function(context, ...args) {
const fn = this
const bindFn = function (...newFnArgs) {
fn.call(
this instanceof bindFn ? this : context,
...args, ...newFnArgs
)
}
bindFn.prototype = Object.create(fn.prototype)
return bindFn
}
模拟new
- 建立一個新的空對象
- 把
綁定到空對象this
- 使空對象的
指向構造函數的原型(__proto__
)prototype
- 執行構造函數,為空對象添加屬性
- 判斷構造函數的傳回值是否為對象,如果是對象,就使用構造函數的傳回值,否則傳回建立的對象
const createNew = (Con, ...args) => {
const obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
模拟instanceOf
- 周遊左邊變量的原型鍊,直到找到右邊變量的 prototype,如果沒有找到,傳回
false
const myInstanceOf = (left, right) => {
let leftValue = left.__proto__
let rightValue = right.prototype
while(true) {
if(leftValue === null) return false
if(leftValue === rightValue) return true
leftValue = leftValue.__proto__
}
}
深拷貝(簡單版)
- 判斷類型是否為原始類型,如果是,無需拷貝,直接傳回
- 為避免出現循環引用,拷貝對象時先判斷存儲空間中是否存在目前對象,如果有就直接傳回
- 開辟一個存儲空間,來存儲目前對象和拷貝對象的對應關系
- 對引用類型遞歸拷貝直到屬性為原始類型
const deepClone = (target, cache = new WeakMap()) => {
if(target === null || typeof target !== 'object') {
return target
}
if(cache.get(target)) {
return target
}
const copy = Array.isArray(target) ? [] : {}
cache.set(target, copy)
Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
return copy
}
深拷貝(尤雨溪版)
vuex源碼
- 原理與上一版類似
function find(list, f) {
return list.filter(f)[0]
}
function deepCopy(obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))
return copy
}
函數防抖
-
繼承自父級上下文,指向觸發事件的目标元素this
- 事件被觸發時,傳入
對象event
- 傳入
參數,判斷是否可以立即執行回調函數,不必要等到事件停止觸發後才開始執行leading
- 回調函數可以有傳回值,需要傳回執行結果
const debounce = (fn, wait = 300, leading = true) => {
let timerId, result
return function(...args) {
timerId && clearTimeout(timerId)
if (leading) {
if (!timerId) result = fn.apply(this, args)
timerId = setTimeout(() => timerId = null, wait)
} else {
timerId = setTimeout(() => result = fn.apply(this, args), wait)
}
return result
}
}
函數節流(定時器)
const throttle = (fn, wait = 300) => {
let timerId
return function(...args) {
if(!timerId) {
timerId = setTimeout(() => {
timerId = null
return result = fn.apply(this, ...args)
}, wait)
}
}
}
函數節流(時間戳)
const throttle = (fn, wait = 300) => {
let prev = 0
let result
return function(...args) {
let now = +new Date()
if(now - prev > wait) {
prev = now
return result = fn.apply(this, ...args)
}
}
}
函數節流實作方法差別
方法 | 使用時間戳 | 使用定時器 |
開始觸發時 | 立刻執行 | n秒後執行 |
停止觸發後 | 不再執行事件 | 繼續執行一次事件 |
數組去重
const uniqBy = (arr, key) => {
return [...new Map(arr.map(item) => [item[key], item])).values()]
}
const singers = [
{ id: 1, name: 'Leslie Cheung' },
{ id: 1, name: 'Leslie Cheung' },
{ id: 2, name: 'Eason Chan' },
]
console.log(uniqBy(singers, 'id'))
// [
// { id: 1, name: 'Leslie Cheung' },
// { id: 2, name: 'Eason Chan' },
// ]
原理是利用
Map
的鍵不可重複
數組扁平化(技巧版)
const flatten = (arr) => arr.toString().split(',').map(item => +item)
數組扁平化
const flatten = (arr, deep = 1) => {
return arr.reduce((cur, next) => {
return Array.isArray(next) && deep > 1 ?
[...cur, ...flatten(next, deep - 1)] :
[...cur, next]
},[])
}
函數柯裡化
const currying = (fn) {
_curry = (...args) =>
args.length >= fn.length
? fn(...args)
: (...newArgs) => _curry(...args, ...newArgs)
}
原理是利用閉包把傳入參數儲存起來,當傳入參數的數量足夠執行函數時,就開始執行函數
釋出訂閱EventEmitter
class EventEmitter {
#subs = {}
emit(event, ...args) {
if (this.#subs[event] && this.#subs[event].length) {
this.#subs[event].forEach(cb => cb(...args))
}
}
on(event, cb) {
(this.#subs[event] || (this.#subs[event] = [])).push(cb)
}
off(event, offCb) {
if (offCb) {
if (this.#subs[event] && this.#subs[event].length)
this.#subs[event] = this.#subs[event].filter(cb => cb !== offCb)
} else {
this.#subs[event] = []
}
}
}
subs
是
EventEmitter
私有屬性(最新特性參考阮一峰老師的ECMAScript 6 入門),通過
on
注冊事件,
off
登出事件,
emit
觸發事件
寄生組合繼承
function Super(foo) {
this.foo = foo
}
Super.prototype.printFoo = function() {
console.log(this.foo)
}
function Sub(bar) {
this.bar = bar
Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
ES6版繼承
class Super {
constructor(foo) {
this.foo = foo
}
printFoo() {
console.log(this.foo)
}
}
class Sub extends Super {
constructor(foo, bar) {
super(foo)
this.bar = bar
}
}
ES5
的繼承,實質是先創造子類的執行個體對象,然後将再将父類的方法添加到
this
上。
ES6
的繼承,先創造父類的執行個體對象(是以必須先調用
super
方法,然後再用子類的構造函數修改
this