前端面试常考的 JavaScript 手写题总汇整
2023年1月7日
💎 加入 E+ 成長計畫 與超過 750+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源
前端手写题是许多面试会考的题型,从最基本上的各类效用函式(例如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;
    }
  };
}