Skip to content

前端笔试题 ​

1. 事件循环 ​

先执行同步代码,当前任务结束后清空微任务,再进入下一个任务。

js
console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve(3).then(console.log);
  console.log(4);
});
console.log(5);
// 1、5、2、4、3
js
async function async1() {
  console.log(1);
  await async2();
  console.log(2);
}
async function async2() { console.log(3); }

console.log(4);
setTimeout(() => console.log(5));
async1();
Promise.resolve().then(() => console.log(7));
console.log(8);
// 4、1、3、8、2、7、5

await 之前同步执行,await 后面的部分进入微任务队列。

2. 实现防抖 ​

防抖的意思是短时间内反复触发,只执行最后一次。搜索输入、窗口 resize 常用。

js
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

apply 保留调用时的 this 和参数。生产版本还可以增加立即执行、取消和 flush 能力。

3. 递归遍历树并生成路径 ​

js
function getPaths(node, parentPath = '') {
  const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name;
  const result = [currentPath];

  for (const child of node.children ?? []) {
    result.push(...getPaths(child, currentPath));
  }
  return result;
}

思路是先保存当前节点路径,再把当前路径传给每个子节点。时间复杂度 O(n)。树很深时可改成栈迭代,避免调用栈溢出。

4. 找数组中出现次数最多的值 ​

js
function mostFrequent(list) {
  if (!list.length) return undefined;

  const counts = new Map();
  let result = list[0];
  let max = 0;

  for (const item of list) {
    const count = (counts.get(item) ?? 0) + 1;
    counts.set(item, count);
    if (count > max) {
      max = count;
      result = item;
    }
  }
  return { value: result, count: max };
}

一次遍历,时间 O(n),空间 O(n)。并列时这版保留最早达到最高次数的值,真实需求要先约定并列规则。

5. 原型链和 this ​

js
const parent = {
  name: 'Parent',
  getName() { return this.name; },
};
const child = Object.create(parent);
child.name = 'Child';

child.getName();                 // Child
parent.getName();                // Parent
Object.getPrototypeOf(child) === parent; // true

child 自己没有 getName,会沿原型链找到它;但调用者仍是 child,所以 this.name 是 Child。实际代码优先用 Object.getPrototypeOf,不要依赖 __proto__。

6. 两数之和 ​

js
function twoSum(nums, target) {
  const indexes = new Map();

  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i];
    if (indexes.has(need)) return [indexes.get(need), i];
    indexes.set(nums[i], i);
  }
  return [];
}

遍历当前值时,检查之前是否见过它的补数。时间 O(n),空间 O(n)。要用 has,不能用真假判断,否则索引 0 会被误判。

7. 实现 Promise.all ​

js
function promiseAll(iterable) {
  const items = Array.from(iterable);
  return new Promise((resolve, reject) => {
    if (items.length === 0) return resolve([]);

    const results = new Array(items.length);
    let completed = 0;

    items.forEach((item, index) => {
      Promise.resolve(item).then(value => {
        results[index] = value;
        completed += 1;
        if (completed === items.length) resolve(results);
      }, reject);
    });
  });
}

重点是保持输入顺序、兼容普通值、空数组立即完成,并在任意一个 Promise 拒绝时让整体拒绝。

8. 对象转 URL 查询参数 ​

js
function toQuery(params) {
  const search = new URLSearchParams();
  for (const [key, value] of Object.entries(params)) {
    if (value == null) continue;
    if (Array.isArray(value)) value.forEach(item => search.append(key, String(item)));
    else search.set(key, String(value));
  }
  return search.toString();
}

URLSearchParams 会负责转义。嵌套对象如何编码没有统一答案,需要和后端约定。

9. 数字每三位加逗号 ​

业务代码优先用国际化 API:

js
new Intl.NumberFormat('en-US').format(10000); // 10,000

只考正则时可以写:

js
String(10000).replace(/\B(?=(\d{3})+(?!\d))/g, ',');

10. 实现深拷贝 ​

现代浏览器优先 structuredClone(value),它支持循环引用、Map、Set、Date 等,但不能克隆函数和 DOM。

js
function deepClone(value, seen = new WeakMap()) {
  if (value === null || typeof value !== 'object') return value;
  if (seen.has(value)) return seen.get(value);
  if (value instanceof Date) return new Date(value);
  if (value instanceof RegExp) return new RegExp(value.source, value.flags);

  const result = Array.isArray(value)
    ? []
    : Object.create(Object.getPrototypeOf(value));
  seen.set(value, result);

  for (const key of Reflect.ownKeys(value)) {
    result[key] = deepClone(value[key], seen);
  }
  return result;
}

这只是常见面试版。完整实现还要考虑属性描述符、Map/Set、TypedArray 等,不建议在生产里重复造轮子。

11. 下划线转驼峰 ​

js
function toCamelCase(text) {
  return text.replace(/_([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
}

toCamelCase('user_name_id'); // userNameId

12. 数组排序 ​

sort 默认按字符串排序,数字必须传比较函数,并且它会修改原数组。

js
const asc = [...numbers].sort((a, b) => a - b);
const desc = [...numbers].sort((a, b) => b - a);