前端面试手写代码通关指南:高频考点解析与实战技巧

39 阅读13分钟

⚠️友情提示:文章内容较长,建议先点赞+收藏后再慢慢享用

一、前言:为什么手写代码是前端面试的关键环节?

前端发展至今,技术栈的快速迭代让我们这些前端牛马可以借助大量现成的工具和框架提升开发效率的同时,也被很多层出不穷的新技术搞得疲惫不堪。其中,手写代码的能力作为很多中大厂技术面试的核心考察点。这一环节虽然深受很多牛马的诟病,但是这一个环节可以直指开发者对前端语言底层原理的理解深度、工程化思维的严谨性以及解决实际问题的能力,试想一下,你如果连一个基本的工具函数在现场解决不了,那么当你在实际工作中遇到各种刁难问题你当如何?企业为何考虑录用你?

所以,对于企业而言,手写代码不仅考察候选人是否 “会用” 某个工具,更关候选人是否真正 “懂” 背后的运行逻辑。例如:

  • 能熟练使用 Promise 开发异步逻辑,但若无法手写实现其核心逻辑,可能暴露对事件循环机制理解的不足;

更关键的是,手写代码的过程能直接反映候选人的代码素养——变量命名是否语义化、边界条件处理是否周全、代码结构是否清晰可维护。这些细节恰恰是区分“普通开发者”与“优秀工程师”的关键标尺。

当你在抱怨很多公司开始一上来就出几道手写题让你做的时候,别人已经在悄悄努力去消化这些题了。想进大公司,这是必不可少的一环,虽然你问答环节回答的头头是道,可手写代码写的一塌糊涂,那么offer将会对你挥一挥手,离你远去。

此文章会从面试官的底层考察逻辑出发,通过高频考点 + 代码思维训练的组合,帮助你建立对前端核心技术的系统性认知。无论你是初出茅庐的新人,还是遭遇瓶颈的资深开发者,都能通过手写代码这一“试金石”,重新审视自己的技术能力体系。


二、高频手写题目分类精讲

🎙️直击考点,敲起来,学起来~~~

1. JavaScript基础篇

1.1. 防抖函数

原理:防抖的原理就是你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行。

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>debounce</title>
  <style>
    #container {
      width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <button id="button">点击取消debounce</button>
  <script src="debounce.js"></script>
</body>
</html>

进阶版核心代码实现

// debounce.js
function debounce(func, wait, immediate) {
  var timer, result;

  var debounced =  function() {
    // DOM对象
    var context = this
    // 自定义参数和Event对象
    var args = arguments;
    if(timer) {
      clearTimeout(timer)
    }

    // 是否需要立即执行
    if (immediate) {
      
      // timer在触发后的wait(ms)是true,所以callNow在这期间为false
      var callNow = !timer
      // 停止触发wait(ms)后,才可以重新触发执行
      timer = setTimeout(function() {
        // 这里timer需要手动设置为null,callNow才能重新执行
        timer = null
      }, wait)
      // 触发的第一时间立即执行
      if (callNow) {
        result = func.apply(context, args)
      }
    } else {
      // 如果不是立即执行,延时wait(ms)以后执行
      timer = setTimeout(function() {
        // 在setTimeout中,this指向window,所以这里需要把外面正确的this指向传递进来
        result = func.apply(context, args)
      }, wait)
    }
    
    return result;
  }

  debounced.cancel = function() {
    clearTimeout(timer)
    timer = null
  }

  return debounced
}

var setUseAction = debounce(getUserAction, 10000, true);

container.onmousemove = setUseAction;

document.getElementById("button").addEventListener('click', function(){
    setUseAction.cancel();
})

核心逻辑实现:

  1. 延时执行
  2. 是否需要立即执行
  3. 有返回值
  4. 取消执行,能取消 debounce 函数,比如说我 debounce 的时间间隔是 10 秒钟,immediate 为 true,这样的话,我只有等 10 秒后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行。

1.2. 节流函数

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta title="sdfdsfs">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>throttle</title>
  <style>
    #container {
      width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <button id="button">点击取消debounce</button>
  <script src="throttle.js"></script>
</body>
</html>

核心代码实现

var count = 1;
var container = document.getElementById('container');

function getUserAction() {
  container.innerHTML = count++;
};

// container.onmousemove = getUserAction;


function throttle(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};
  // leading 代表首次是否执行,trailing 代表结束后是否再执行一次
  var later = function() {
      previous = options.leading === false ? 0 : new Date().getTime();
      timeout = null;
      func.apply(context, args);
      if (!timeout) context = args = null;
  };

  var throttled = function() {
      var now = new Date().getTime();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
          if (timeout) {
              clearTimeout(timeout);
              timeout = null;
          }
          previous = now;
          func.apply(context, args);
          if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
      }
  };
  return throttled;
}

// container.onmousemove = throttle(getUserAction, 1000);
// container.onmousemove = throttle(getUserAction, 1000, {
//     leading: false
// });
container.onmousemove = throttle(getUserAction, 1000, {
    trailing: false
});

其实在防抖函数中我们就已经实现了节流的功能,大家可以自己体验一下。如果整个代码看不懂,可以试着去做拆分,一步步实现。

防抖和节流函数中涉及了定时器,this指向,event参数,匿名函数的参数如何传递,立即执行函数等等知识点,还是值得好好参悟一下。

【参考文章】

1.3. 深拷贝实现

  • 实现基础的循环拷贝
  • 如果是原始类型,无需继续拷贝,直接返回
  • 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上
  • 考虑数组
  • 循环引用
  • 性能优化-把 for in 循环换成while循环

function clone(target, map = new WeakMap()) {
  if (typeof target === 'object') {
    const isArray = Array.isArray(target)
    let cloneTarget = isArray ? [] : {}
    if (map.get(target)) {
      return map.get(target)
    }
    map.set(target, cloneTarget)
    const keys = isArray ? undefined : Object.keys(target)
    myfor(keys || target, (value, key) => {
      if (keys) {
        key = value
      }
      cloneTarget[key] = clone(target[key], map)
    })
    return cloneTarget
  } else {
    return target
  }
}

// =======测试用例=========
const target = {
  field1: 1,
  field2: undefined,
  field3: {
      child: 'child'
  },
  field4: [2, 4, 8],
  f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } },
};

target.target = target; // 循环引用,如果没有处理,会导致clone递归进入死循环导致栈内存溢出

console.time();
const result2 = clone(target);
console.timeEnd();

这里如果你能知道while > for > for in三者之间的性能对比,用到clone函数中,对面试官来说就是降维打击啊,一定要掌握这个知识点。

大家感兴趣可以自己去测试一下代码:

// 效率对比

// while > for > for in

function testWhile(n) {
  const startTime = new Date()
  let i = 0, sum = 0

  while(i < n) {
    i++;
    sum += 1
  }
  const endTime = new Date()
  const duration = endTime - startTime
  console.log(`while循环执行了${duration}ms`)
}


function testFor(n) {
  const startTime = new Date()
  let sum = 0
  for(let i = 0; i < n; i++) {
    sum += 1
  }
  const endTime = new Date()
  const duration = endTime - startTime
  console.log(`for循环执行了${duration}ms`)
}

function testForIn(n) {
  const startTime = new Date()
  let sum = 0
  const arr = Array(n).fill(1)
  for(i in arr) {
    sum += 1
  }
  const endTime = new Date()
  const duration = endTime - startTime
  console.log(`forIn循环执行了${duration}ms`)
}

let n = 10000000;
testWhile(n);
testFor(n);
testForIn(n);

【参考文章】

1.3. Promise 相关

一、手写 Promise

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._value = undefined    // 储存then回调return的值
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = FULFILLED              // 变更状态
        this._value = val                     // 储存当前value

        // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
        // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
        while(this._resolveQueue.length) {    
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // 实现同resolve
    let _reject = (val) => {
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = REJECTED               // 变更状态
        this._value = val                     // 储存当前value
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }

  //catch方法其实就是执行一下then的第二个回调
  catch(rejectFn) {
    return this.then(undefined, rejectFn)
  }

  //finally方法
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),             //执行回调,并returnvalue传递给后面的then
      reason => MyPromise.resolve(callback()).then(() => { throw reason })  //reject同理
    )
  }

  //静态的resolve方法
  static resolve(value) {
    if(value instanceof MyPromise) return value //根据规范, 如果参数是Promise实例, 直接return这个实例
    return new MyPromise(resolve => resolve(value))
  }

  //静态的reject方法
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
  }
}

二、手写 Promise.all

实现一个 Promise.all 方法,跑通下面的测试代码

const promise1 = Promise.resolve(3);
const promise2 = 42;

const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// 原声调用实现方式
// Promise.all([promise1, promise2, promise3]).then((values) => {
//   console.log(values);
// });

// Expected output: Array [3, 42, "foo"]

代码实现

function myAll(promises) {
  let result = [];   
  let index = 0;
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i])
        .then(res => {
          // 输出结果的顺序和promises的顺序一致
          result[i] = res;
          index++;
          if (index === promises.length) {
            resolve(result);
          }
        })
        .catch(err => {
          reject(err);
        });
    }
  });
}
myAll([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});

三、手写 Promise.race

// race:返回 promise 列表中第一个执行完的结果
class Promise {
  static race(promises) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        // Promise.resolve包一下,防止promises[i]不是Promise类型
        Promise.resolve(promises[i])
          .then(res => {
            resolve(res);
          })
          .catch(err => {
            reject(err);
          });
      }
    });
  }
}

四、手写 Promise.allSettled

// allSettled: 不管成功失败,一起返回成功
function promiseAllSettled(promises) {
  const result = [];
  for (let p of promises) {
    result.push(
      Promise.resolve(p).then(
        (value) => ({ value, status: "FULFILLED" }),
        (reason) => ({ reason, status: "REJECTED" })
      )
    );
  }
  return Promise.all(result);
}

五、实现一个异步任务调度器🌟

设计一个异步任务的调度器,最多同时有两个异步任务在执行

  • 待执行的任务按照添加顺序依执行。
  • 使测试用例满足输出顺序。

测试用例

class Scheduler {
  constructor() {
    // 请补充...
  }

  async add(promiseCreator) {
    // 请补充...
  }
}

//测试用例:
const scheduler = new Scheduler();

const task = ms =>
  new Promise(resolve => {
    setTimeout(resolve, ms);
  });

const addTask = (ms, order) => {
  scheduler.add(() => task(ms)).then(() => console.log(ms, order));
};

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 输出:
// 500 '2'
// 300 '3'
// 1000 '1'
// 400 '4'

代码实现

class Scheduler {
  constructor() {
    this.queue = []; // 存储等待执行的任务
    this.maxCount = 2; // 最大并行任务数
    this.runningCount = 0; // 当前正在执行的任务数
  }

  // 添加任务到调度器
  async add(promiseCreator) {
    // 如果当前正在执行的任务数达到最大限制,将任务添加到队列中
    if (this.runningCount >= this.maxCount) {
      await new Promise(resolve => this.queue.push(resolve));
    }
    // 执行任务
    this.runningCount++;
    const result = await promiseCreator();
    this.runningCount--;

    // 任务完成后,如果有等待中的任务,从队列中取出并执行
    if (this.queue.length > 0) {
      const next = this.queue.shift();
      next();
    }
    return result;
  }
}

// 测试用例
const scheduler = new Scheduler();

const task = ms =>
  new Promise(resolve => {
    setTimeout(resolve, ms);
  });

const addTask = (ms, order) => {
  scheduler.add(() => task(ms)).then(() => console.log(ms, order));
};

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');

// 预期输出:
// 500 '2'
// 300 '3'
// 1000 '1'
// 400 '4'

六、红绿灯

function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}

const task = (timer, light) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      if (light === "red") {
        red();
      } else if (light === "green") {
        green();
      } else if (light === "yellow") {
        yellow();
      }
      resolve();
    }, timer);
  });

const taskRunner = async () => {
  await task(3000, "red");
  await task(1000, "green");
  await task(2000, "yellow");
  taskRunner();
};

taskRunner();

七、请求超时重试


/**
 * @param {一次请求的最大响应时间} time 
 * @param {最大超时请求次数} limit 
 * @param {资源加载函数} fn 
 * @returns Promise
 */
function request(time, limit, fn) {
  let retryCount = 0;

  async function tryRequest() {
    try {
      return await Promise.race([fn(), timeoutFail(time)]);
    } catch (error) {
      retryCount++;
      if (retryCount <= limit) {
        console.log(`Retry #${retryCount}`);
        return tryRequest();
      } else {
        throw new Error("Max retry limit reached");
      }
    }
  }

  return tryRequest();
}

function timeoutFail(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("Request timed out"));
    }, time);
  });
}
function sleep(time = 2000, val = 1) {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(val);
    }, time)
  );
}

// 调用 sleep 函数,最多重试 3 次,每次超时等待 1000 毫秒
request(1000, 3, sleep)
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error.message);
  });

八、请求并发控制

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>并发请求控制</title>
  </head>
  <body>
    <script>
      // 并发请求函数
      const concurrencyRequest = (urls, maxNum) => {
        return new Promise((resolve) => {
          if (urls.length === 0) {
            resolve([]);
            return;
          }
          const results = [];
          let index = 0; // 下一个请求的下标
          let count = 0; // 当前请求完成的数量

          // 发送请求
          async function request() {
            if (index === urls.length) return;
            const i = index; // 保存序号,使result和urls相对应
            const url = urls[index];
            index++;
            console.log(url);
            try {
              const resp = await fetch(url);
              // resp 加入到results
              results[i] = resp;
            } catch (err) {
              // err 加入到results
              results[i] = err;
            } finally {
              count++;
              // 判断是否所有的请求都已完成
              if (count === urls.length) {
                console.log("完成了");
                resolve(results);
              }
              request();
            }
          }

          // maxNum和urls.length取最小进行调用
          const times = Math.min(maxNum, urls.length);
          for (let i = 0; i < times; i++) {
            request();
          }
        });
      };

      // 测试
      const urls = [];
      for (let i = 1; i <= 20; i++) {
          urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }
      concurrencyRequest(urls, 3).then(res => {
          console.log(res);
      })

    </script>
  </body>
</html>

九、常见异步输出题目

第一题

Promise.resolve()
  .then(function() {
    console.log("promise0");
  })
  .then(function() {
    console.log("promise5");
  });

setTimeout(() => {
  console.log("timer1");
  Promise.resolve().then(function() {
    console.log("promise2");
  });
  Promise.resolve().then(function() {
    console.log("promise4");
  });
}, 0);

setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(function() {
    console.log("promise3");
  });
}, 0);

Promise.resolve().then(function() {
  console.log("promise1");
});
console.log("start");

这个代码的执行结果和完整分析如下:

打印结果

start
promise0
promise5
promise1
timer1
promise2
promise4
timer2
promise3

执行过程完整分析

  1. 初始执行阶段(同步代码)
  • 首先执行同步代码 console.log("start"),打印 start
  • 遇到Promise和setTimeout都会被放入相应的任务队列,不会立即执行
  1. 微任务队列处理 执行完同步代码后,JavaScript引擎会先处理微任务队列中的所有任务:

第一轮微任务:

  • 执行第一个Promise链的第一个then:打印 promise0
  • 执行最后一个Promise.resolve().then:打印 promise1

第二轮微任务:

  • 执行第一个Promise链的第二个then:打印 promise5
  1. 宏任务队列处理 微任务队列清空后,开始处理宏任务队列:

第一个setTimeout执行:

  • 打印 timer1
  • 创建两个新的Promise微任务
  • 处理这两个微任务:
    • 打印 promise2
    • 打印 promise4

第二个setTimeout执行:

  • 打印 timer2
  • 创建一个新的Promise微任务
  • 处理这个微任务:
    • 打印 promise3

关键知识点:

  1. 事件循环机制:JavaScript是单线程的,通过事件循环来处理异步任务

  2. 任务优先级

    • 同步代码 > 微任务(Promise.then) > 宏任务(setTimeout)
    • 每次宏任务执行完后,都会清空所有微任务再执行下一个宏任务
  3. Promise链式调用:每个.then()都会创建一个新的微任务

  4. setTimeout(0):即使延时为0,也会被放入宏任务队列,在当前执行栈和微任务队列清空后才执行

第二题

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)

执行结果

script start
async1
promise1
script end
1
timer2
timer1

过程分析:

  1. 同步代码执行阶段

第1步: 执行 console.log('script start')

  • 输出:script start

第2步: 调用 async1()

  • 进入async1函数
  • 执行 console.log('async1'),输出:async1
  • 遇到 setTimeout(() => { console.log('timer1') }, 2000),将其放入宏任务队列(2秒后执行)
  • 遇到 await new Promise(resolve => { console.log('promise1') })
    • 执行Promise构造函数中的同步代码:console.log('promise1'),输出:promise1
    • 注意:这个Promise没有调用resolve(),所以会一直处于pending状态
    • await会等待这个Promise resolve,但由于Promise永远不会resolve,所以async1函数会在这里"卡住"
    • console.log('async1 end')return 'async1 success' 永远不会执行

第3步: 执行 console.log('script end')

  • 输出:script end

第4步: 执行Promise链

Promise.resolve(1)
  .then(2)              // 2不是函数,会被忽略,值1会传递下去
  .then(Promise.resolve(3))  // Promise.resolve(3)不是函数,会被忽略,值1继续传递
  .catch(4)             // 没有错误,catch被跳过
  .then(res => console.log(res))  // res = 1
  • 这个Promise链会创建微任务,输出:1

第5步: 遇到第二个setTimeout

  • setTimeout(() => { console.log('timer2') }, 1000) 被放入宏任务队列(1秒后执行)
  1. 微任务队列处理
  • Promise.resolve(1)的链式调用产生的微任务被执行
  • 输出:1
  1. 宏任务队列处理
  • 1秒后,第二个setTimeout执行,输出:timer2
  • 2秒后,第一个setTimeout执行,输出:timer1

关键问题分析

  1. async1函数为什么没有完全执行?

    • await new Promise(resolve => { console.log('promise1') }) 中的Promise没有调用resolve()
    • await会无限等待这个Promise resolve
    • 因此 console.log('async1 end') 和后续代码永远不会执行
    • async1().then(res => console.log(res)) 中的then回调也永远不会执行
  2. Promise.then()的参数处理

    • .then(2) 中的2不是函数,会被忽略
    • .then(Promise.resolve(3)) 中Promise.resolve(3)也不是函数,会被忽略
    • 原始值1会一直传递到最后的then回调
  3. 事件循环优先级

    • 同步代码 > 微任务 > 宏任务
    • setTimeout的执行顺序按照延迟时间排序

这个例子很好地展示了async/await中Promise必须resolve才能继续执行,以及事件循环中任务的执行优先级。

以上两个代码输出题目搞懂了,基本上所有的类似题型100%拿下。

2. 数据处理篇

  • 数组扁平化(递归/迭代/Generator实现)
  • 手写数组常用方法(map/filter/reduce)
  • 对象属性路径解析(a.b.c格式处理)
  • 模板字符串解析(含嵌套表达式)

3. 框架相关篇

  • 虚拟DOM的diff算法实现
  • 响应式数据监听(Proxy版)
  • 简易版Vue双向绑定
  • 手写Redux核心逻辑

✨持续更新中...

三、总结:从“能写”到“写好”的思维跃迁

手写代码的终极目标,绝不是为了在面试中复现一段“标准答案”,而是通过反复的原理推演-逻辑拆解-代码实现循环,培养对技术的深度思考能力。

核心价值的再认知

  1. 技术理解的标尺:手写实现是检验“知识幻觉”的最佳方式,能清晰暴露对 this 指向、闭包、原型链等核心概念的理解盲区。有可能你觉得这段代码你看了后懂了,但是当你自己写的时候却不知从何下手了。
  2. 工程思维的训练场:从简单的深拷贝到复杂的虚拟DOM Diff,代码的健壮性(异常处理)、可扩展性(设计模式应用)在反复迭代中自然提升。
  3. 沟通能力的延伸:手写过程中与面试官的交互(需求确认、边界讨论),本质上是对技术方案评审能力的预演。

持续精进的建议

  • 建立“源码级”学习习惯:阅读 Lodash 的工具函数、React 的核心模块源码,观察工业级代码的异常处理与性能优化技巧。
  • 从“解题”到“造题”:尝试将工作中遇到的复杂逻辑(如表单联动校验、异步任务调度)抽象为手写题目,反向设计实现方案。
  • 拥抱“不完美”迭代:初期的实现可以优先保证功能性的完整,然后再逐步优化性能(如用 Map 替代递归缓存)、增强扩展性(支持配置化)、代码优雅性等等。

写给焦虑者的结语

“手写代码”的难关,本质是前端开发者必经的成人礼。它会逼迫你褪去框架的“保护壳”(框架虽然提升了效率,但是也逐渐让你丧失了野生的原始本能),用更严谨的逻辑和清晰的表达,将一些复杂的需求转化为可靠的代码。

当你再次面对一道手写题时,不妨将其视为一次技术对话

  • 对语言特性,问自己:“ECMAScript规范中如何定义这一行为?”
  • 对代码设计,问自己:“如果需求变更,哪些部分需要扩展?哪些可以复用?”
  • 对工程实践,问自己:“这段代码会在哪个环节崩溃?如何让它更抗风险?”

记住,面试官期待的从来不是完美无缺的代码,而是一个能持续进化、知其然亦知其所以然的Problem Solver。愿本文为你点亮一盏灯,但真正的光,终将来自你笔下每一行深思熟虑的代码。

最后,光看是不行的,你要动动你的小手和小脑袋瓜子,去做,去思考,你的技术才能慢慢积累和成长,你的钱包也会越来越鼓,相信我!

💗完整代码仓库:github.com/xiumubai/co…