前端面試常考的 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;
}
};
}