前端面试常考的 JavaScript 手写题总汇整

2023年1月7日

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

前端手写题是许多面试会考的题型,从最基本上的各类效用函式(例如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) 方法》](https://www.explainthis.io/zh-hans/swe /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)》](https://www.explainthis.io/zh- hans/swe/shallow-copy-and-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)》](https://www.explainthis.io/zh- hans/swe/shallow-copy-and-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 上追蹤我們