前端面試常考的 JavaScript 手寫題總彙整

2023年1月7日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

前端手寫題是許多面試會考的題型,從最基本上的各類效用函式 (例如 debouncecurrydeepClone 等等),到常見的 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;
    }
  };
}
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們