前端面试常考的 JavaScript 手写题总汇整
2023年1月7日
💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源
前端手写题是许多面试会考的题型,从最基本上的各类效用函式(例如debounce
、 curry
、 deepClone
等等),到常见的 JavaScript 方法(例如Promise.all
),都不能只会用,还要会自己手写出来。本篇文章将会节录常见的手写题目,并附上解答。除此之外,每一题也都有详细解说版的连结,供读者们参考。
Promise.all
以下的解答为精简版 (仅处理 Array),但实际上 Promise.all
可以接受任何可迭代的类型 (Iterable),例如 Map、Set。完整版的解答可以参考这篇详解《请实现 Promise.all》。
function promiseAll(promises) {
if (!Array.isArray(promises)) {
return new TypeError("Arguments must be an array");
}
if (promises.length === 0) {
return Promise.resolve([]);
}
const outputs = [];
let resolveCounter = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise
.then((value) => {
outputs[index] = value;
resolveCounter += 1;
if (resolveCounter === promises.length) {
resolve(outputs);
}
})
.catch(reject);
});
});
}
Promise.race
详细解说版请见《请实现 Promise.race》
function promiseRace(promises) {
return new Promise((resolve, reject) => {
for (const p of promises) {
p.then((val) => {
resolv(val);
}).catch((e) => {
reject(e);
});
}
});
}
Lodash 的 .get()
这题多半会是有给定 castPath
的版本,所以输入与输出会如下
// 给定一个物件,例如
const object = { a: [{ b: { c: 3 } }] };
// 给一个路径,透过 .get() 找到该路径的值,例如
get(object, ["a", "0", "b", "c"]); // 回传 3
这时可以这样写 lodashGet
,解说版详见 《请实现 Lodash 的 .get()》
function lodashGet(object, path, defaultValue) {
if (object == null) {
return defaultValue;
}
let count = 0;
const length = path.length;
while (object != null && count < length) {
object = object[path[count++]];
}
const result = count && count == length ? object : undefined;
return result === undefined ? defaultValue : result;
}
手写 debounce
详细解说版请见《手写防抖 (debounce) 函式》
function debounce(fn, delay = 500) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}
手写 throttle
详细解说版请见《手写节流 (throttle) 函式》
function throttle(fn, delay = 500) {
let timer = null;
return (...args) => {
if (timer) return;
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
};
}
数组去除重复 (remove duplicates) 方法
以下只列出一种方法,实际有很多做法,详细解说版请见《请分享你知道的数组去除重复(remove duplicates) 方法》。
function removeDuplicate(arr) {
return Array.from(new Set(arr));
}
let arr = [1, 2, 3, 2, 3, 8];
let arrAfter = removeDuplicate(arr);
console.log(arr1After); // [1, 2, 3, 8]
浅拷贝 (shallow copy) 方法
以下只列出一种方法,实际有很多做法,详细解说版请见《请实践 JavaScript 浅拷贝(shallow copy) 和深拷贝(deep copy)》。
let objA = {
a: 1,
b: { c: 3 },
};
let objB = Object.assign({}, objA);
console.log(objA === objB); // false
深拷贝 (deep copy) 方法
以下只列出一种方法,实际有很多做法,详细解说版请见《请实践 JavaScript 浅拷贝(shallow copy) 和深拷贝(deep copy)》。
let objA = {
a: 1,
b: { c: 3 },
};
function deepCopy(item) {
return JSON.parse(JSON.stringify(item));
}
let objB = deepCopy(objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // false
请实践 lodash 的深比较 (isEqual)
详细解说版请见《请实践 lodash 的深比较 (isEqual)》
function isEqual(value, other) {
if (typeof value !== "object" && typeof other !== "object") {
const isValueNaN = Number.isNaN(value);
const isOtherNaN = Number.isNaN(other);
if (isValueNaN && isOtherNaN) {
return true;
}
return value === other;
}
if (value === null && other === null) {
return true;
}
if (typeof value !== typeof other) {
return false;
}
if (value === other) {
return true;
}
if (Array.isArray(value) && Array.isArray(other)) {
if (value.length !== other.length) {
return false;
}
for (let i = 0; i < value.length; i++) {
if (!isEqual(value[i], other[i])) {
return false;
}
}
return true;
}
if (Array.isArray(value) || Array.isArray(other)) {
return false;
}
if (Object.keys(value).length !== Object.keys(other).length) {
return false;
}
for (const [k, v] of Object.entries(value)) {
if (!(k in other)) {
return false;
}
if (!isEqual(v, other[k])) {
return false;
}
}
return true;
}
手写函式缓存 (cache function)
详细解说版请见《手写函式缓存 (cache function)》
function cached(fn) {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
} else {
const val = fn(...args);
cache[key] = val;
return val;
}
};
}