- 防抖
function debounce(func, ms = 500) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
}, ms);
};
}
- 節(jié)流
function throttle(func, ms) {
let canRun = true;
return function (...args) {
if (!canRun) return;
canRun = false;
setTimeout(() => {
func.apply(this, args);
canRun = true;
}, ms);
};
}
- new
function myNew(Func) {
const instance = {};
if (Func.prototype) {
Object.setPrototypeOf(instance, Func.prototype);
}
const res = Func.apply(instance, [].slice.call(arguments, 1));
if (typeof res === "function" || (typeof res === "object" && res !== null)) {
return res;
}
return instance;
}
- bind
Function.prototype.myBind = function (context = globalThis) {
const fn = this;
const args = Array.from(arguments).slice(1);
const newFunc = function () {
if (this instanceof newFunc) {
// 通過 new 調(diào)用,綁定 this 為實例對象
fn.apply(this, args);
} else {
// 通過普通函數(shù)形式調(diào)用,綁定 context
fn.apply(context, args);
}
};
// 支持 new 調(diào)用方式
newFunc.prototype = fn.prototype;
return newFunc;
};
- call
Function.prototype.myCall = function (context = globalThis) {
// 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context
context.fn = this;
let args = [].slice.call(arguments, 1);
let res = context.fn(...args);
delete context.fn;
return res;
};
- apply
Function.prototype.myApply = function (context = globalThis) {
// 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context
context.fn = this;
let res;
if (arguments[1]) {
res = context.fn(...arguments[1]);
} else {
res = context.fn();
}
delete context.fn;
return res;
};
- deepCopy
function deepCopy(obj, cache = new WeakMap()) {
if (!obj instanceof Object) return obj;
// 防止循環(huán)引用
if (cache.get(obj)) return cache.get(obj);
// 支持函數(shù)
if (obj instanceof Function) {
return function () {
obj.apply(this, arguments);
};
}
// 支持日期
if (obj instanceof Date) return new Date(obj);
// 支持正則對象
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
// 還可以增加其他對象,比如:Map, Set等,根據(jù)情況判斷增加即可,面試點到為止就可以了
// 數(shù)組是 key 為數(shù)字素銀的特殊對象
const res = Array.isArray(obj) ? [] : {};
// 緩存 copy 的對象,用于出來循環(huán)引用的情況
cache.set(obj, res);
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof Object) {
res[key] = deepCopy(obj[key], cache);
} else {
res[key] = obj[key];
}
});
return res;
}
- 事件總線 | 發(fā)布訂閱模式
class EventEmitter {
constructor() {
this.cache = {};
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn);
} else {
this.cache[name] = [fn];
}
}
off(name, fn) {
const tasks = this.cache[name];
if (tasks) {
const index = tasks.findIndex((f) => f === fn || f.callback === fn);
if (index >= 0) {
tasks.splice(index, 1);
}
}
}
emit(name) {
if (this.cache[name]) {
for (let fn of this.cache[name]) {
fn();
}
}
}
emit(name, once = false) {
if (this.cache[name]) {
// 創(chuàng)建事件副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊相同事件,觸發(fā)時,會造成死循環(huán)
const tasks = this.cache[name].slice()
for (let fn of tasks) {
fn();
}
if (once) {
delete this.cache[name]
}
}
}
}
- 柯里化:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
func.apply(this, args);
} else {
return function (...args2) {
curried.apply(this, args.concat(args2));
};
}
};
}
- es5 實現(xiàn)繼承
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
// Parent
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function () {
console.log(this.name);
};
// Child
function Child(age, name) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.say = function () {
console.log(this.age);
};
- instanceof
function instanceOf(instance, klass) {
let proto = instance.__proto__;
let prototype = klass.prototype;
while (true) {
if (proto === null) return false;
if (proto === prototype) return true;
proto = proto.__proto__;
}
}
- 異步并發(fā)數(shù)限制
/**
* 關(guān)鍵點說明
* 1. new promise 一經(jīng)創(chuàng)建,立即執(zhí)行
* 2. 使用 Promise.resolve().then 可以把任務(wù)加到微任務(wù)隊列,防止立即執(zhí)行迭代方法
* 3. 微任務(wù)處理過程中,產(chǎn)生的新的微任務(wù),會在同一事件循環(huán)內(nèi),追加到微任務(wù)隊列里
* 4. 使用 race 在某個任務(wù)完成時,繼續(xù)添加任務(wù),保持任務(wù)按照最大并發(fā)數(shù)進行執(zhí)行
* 5. 任務(wù)完成后,需要從 doingTasks 中移出
*/
function limit(count, array, iterateFunc) {
const tasks = [];
const doingTasks = [];
let i = 0;
const enqueue = () => {
if (i === array.length) {
return Promise.resolve();
}
const task = Promise.resolve().then(() => iterateFunc(array[i++]));
tasks.push(task);
const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1));
doingTasks.push(doing);
const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve();
return res.then(enqueue);
};
return enqueue().then(() => Promise.all(tasks));
}
// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
limit(4, [1000, 1000, 1000, 1000], timeout).then((res) => {
console.log(res);
})
- 異步串行 | 異步并行
// 大廠面試題,實現(xiàn)一個異步加法
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 1000);
}
// 0. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
asyncAdd(a, b, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
// 1. 串行處理
async function serialSum(...args) {
return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}
// 2. 并行處理
async function parallelSum(...args) {
if (args.length === 1) return args[0]
const tasks = []
for (let i = 0; i < args.length; i += 2) {
tasks.push(promiseAdd(args[i], args[i + 1] || 0))
}
const results = await Promise.all(tasks)
return parallelSum(...results)
}
// 測試
(async () => {
const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res1)
const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res2)
})()
- vue reactive
// Dep module
class Dep {
static stack = []
static target = null
deps = null
constructor() {
this.deps = new Set()
}
depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(w => w.update())
}
static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}
static popTarget() {
this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (isObj(val)) {
reactive(val)
}
}
// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}
update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 測試代碼
const data = reactive({
msg: 'aaa'
})
new Watcher(() => {
console.log('===> effect', data.msg);
})
setTimeout(() => {
data.msg = 'hello'
}, 1000)
- promise
// 建議閱讀 [Promises/A+ 標(biāo)準(zhǔn)](https://promisesaplus.com/)
class MyPromise {
constructor(func) {
this.status = 'pending'
this.value = null
this.resolvedTasks = []
this.rejectedTasks = []
this._resolve = this._resolve.bind(this)
this._reject = this._reject.bind(this)
try {
func(this._resolve, this._reject)
} catch (error) {
this._reject(error)
}
}
_resolve(value) {
setTimeout(() => {
this.status = 'fulfilled'
this.value = value
this.resolvedTasks.forEach(t => t(value))
})
}
_reject(reason) {
setTimeout(() => {
this.status = 'reject'
this.value = reason
this.rejectedTasks.forEach(t => t(reason))
})
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.resolvedTasks.push((value) => {
try {
const res = onFulfilled(value)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
} catch (error) {
reject(error)
}
})
this.rejectedTasks.push((value) => {
try {
const res = onRejected(value)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
reject(res)
}
} catch (error) {
reject(error)
}
})
})
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
// 測試
new MyPromise((resolve) => {
setTimeout(() => {
resolve(1);
}, 500);
})
.then((res) => {
console.log(res);
return new MyPromise((resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
});
})
.then((res) => {
console.log(res);
}, err => {
console.log('==>', err);
});