前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础进阶

    • 基础篇
    • 进阶篇
    • 高频篇
    • 手写篇
      • 1 实现防抖函数(debounce)
      • 2 实现节流函数(throttle)
      • 3 实现instanceOf
      • 4 实现new的过程
      • 5 实现call方法
      • 6 实现apply方法
      • 7 实现bind方法
      • 8 实现深拷贝
        • 1 简洁版本
        • 2 实现完整的深拷贝
      • 9 实现类的继承
      • 10 实现Promise相关方法
      • 11 实现发布订阅模式
      • 12 实现观察者模式
      • 13 实现单例模式
      • 14 实现Ajax
      • 15 实现JSONP方法
      • 16 实现async/await
      • 17 基于Generator函数实现async/await原理
      • 18 实现ES6的const
      • 19 实现一个迭代器生成函数
      • 20 实现ES6的extends
      • 21 实现Object.create
      • 22 实现Object.freeze
      • 23 实现Object.is
      • 24 实现一个compose函数
      • 25 setTimeout与setInterval实现
      • 26 实现Node的require方法
      • 27 实现LRU淘汰算法
      • 28 框架相关
      • 29 数组相关
      • 30 正则相关
      • 31 函数柯里化相关
      • 32 字符串相关
      • 33 实现工具函数
      • 34 手写常见排序
      • 35 算法数据结构
      • 36 综合
    • 设计模式
  • 精选模块

# 面试高频手写题

手写题别急着背完整代码。先用一句话说清输入、输出和最容易踩的边界,再关掉答案自己写一遍;写完拿空值、重复调用和异步顺序试一试。面试时就算没写完,能把思路和取舍讲明白,也比默写一段记不住的实现更有帮助。

建议优先掌握:

  • instanceof - 考察对原型链的理解
  • new - 对创建对象实例过程的理解
  • call/apply/bind - 对this指向的理解
  • 手写promise - 对异步的理解
  • 手写原生ajax - 对ajax原理和http请求方式的理解,重点是get和post请求的实现

# 1 实现防抖函数(debounce)

30 秒速记

  • 防抖 = 电梯关门:有人进来就重新等,停止触发 wait 毫秒后才执行最后一次
  • 核心三步:clearTimeout(timer) → timer = setTimeout(...) → 到点 fn.apply(this, args)
  • 返回普通函数(不是箭头函数)才能拿到调用时的 this;参数用 ...args 传最后一次的
  • 进阶:immediate 首次立即执行、cancel() 取消、flush() 立刻执行挂起的那次
  • 场景:搜索联想、输入校验、resize 结束后重算;React 里要用 useMemo / useRef 保持同一个防抖实例

防抖就是事件连续触发时一直重新计时,停下来超过 wait 才执行一次。 像电梯关门,只要还有人进来就重新等。实现就是闭包里存一个 timer,每次调用先 clearTimeout,再开新的 setTimeout,到点用 fn.apply(this, args) 执行,这样 this 和最后一次的参数都对。和节流的区别是:防抖只要最后一次,持续触发就一直不执行;节流是按固定间隔执行。React 里有个常见坑,每次渲染都 debounce(fn) 出一个新函数,计时器各自独立,等于没防。

防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算

防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行

eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

手写简化版:

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
  // 缓存一个定时器id
  let timer = 0
  // 这里返回的函数是每次用户实际调用的防抖函数
  // 如果已经设定过定时器了就清空上一次的定时器
  // 开始一个新的定时器,延迟执行用户传入的方法
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

适用场景:

  • 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好
  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

面试时写出基础版之后,面试官多半会追问「第一次能不能立刻执行」「能不能取消」。下面这版把两点都补上,代码不长,建议能默写:

function debounce(fn, wait = 300, immediate = false) {
  let timer = null

  function debounced(...args) {
    // 立即执行版:当前没有计时器,说明是一串触发里的第一次
    const callNow = immediate && !timer
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      // 非立即版在这里执行最后一次
      if (!immediate) fn.apply(this, args)
    }, wait)
    if (callNow) return fn.apply(this, args)
  }

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

  return debounced
}

const log = debounce((v) => console.log('search', v), 300)
log('a'); log('ab'); log('abc')
// 300ms 后只输出一次:search abc

const submit = debounce(() => console.log('submit'), 1000, true)
submit(); submit(); submit()
// 立刻输出一次 submit,1 秒内后面两次被吞掉
@前端进阶之旅: 代码已经复制到剪贴板

几个容易丢分的点:返回的 debounced 必须是普通函数,箭头函数拿不到调用时的 this;定时器回调用箭头函数,才能沿用外层的 this 和 args;立即执行版要在定时器到点时把 timer 置空,否则下一轮第一次触发不会立即执行。在 React 里使用时,防抖实例要用 useMemo 或 useRef 固定住,组件卸载时调用 cancel(),避免卸载后还去 setState。

面试官追问

追问 1搜索框为什么用防抖不用节流?
参考回答

产品要的是用户停下来后只查最后一次。节流在输入过程中每隔一段时间就会发一次,中间那些请求都是浪费。防抖的代价是停顿前看不到中间结果,一般 300ms 左右体感就可以。

追问 2组件里写 const onSearch = debounce(fetchList, 300),结果每个字都发了请求,为什么?
参考回答

每次渲染都创建一个新的防抖函数,新函数清不掉旧函数的 timer,等于没防。用 useMemo(() => debounce(fetchList, 300), []) 或 useRef 保持同一个实例,卸载时调 cancel()。

追问 3提交按钮想防重复点击,但第一次点击要立刻有反应,怎么改?
参考回答

尾部防抖会让第一次点击延迟 wait 才执行。要用立即执行版:timer 为空时直接执行,然后开计时器,wait 内的点击只重置计时。不过防重复提交更靠谱的是请求期间禁用按钮,再加服务端幂等。

追问 4防抖后的函数有返回值,调用方拿得到吗?
参考回答

尾部执行版拿不到,原函数是在定时器里执行的,调用时它还没跑。立即执行版可以同步返回;要拿尾部那次的结果,就让防抖函数返回一个 Promise,到点后 resolve。

追问 5搜索联想加了防抖,偶尔还是显示上一个关键词的结果,为什么?
参考回答

防抖只减少请求数,管不了返回顺序。前一个请求慢、后一个快,旧结果后到就把新结果盖了。每次请求带上序号,返回时不是最新的就丢弃,或者用 AbortController 取消上一个请求。

# 2 实现节流函数(throttle)

30 秒速记

  • 节流 = 水龙头限流:持续触发时每隔 wait 最多执行一次,不会一直拖着不执行
  • 时间戳版:now - last >= wait 才执行,首次立即执行,但停止前的最后一次可能丢
  • 定时器版:没有挂起任务时才开 setTimeout,首次要等 wait,停止后还会补一次
  • 完整版两者结合:首次立即、末尾补一次(lodash 的 leading / trailing)
  • 场景:滚动加载、拖拽、resize、埋点上报;和视觉动画相关的优先考虑 requestAnimationFrame

节流是不管事件触发多频繁,每隔一段时间最多只执行一次。 有两种写法:时间戳版记录上次执行时间,间隔够了就执行,所以第一次立刻响应,但停止触发前最后那次如果没到间隔就丢了;定时器版是没有挂起任务时才开一个 setTimeout,第一次要等,停下后还会补执行一次。拖拽这种既要跟手又要落到终点的场景,单用哪种都不完整,得把两种结合起来。和防抖比,节流能在持续触发的过程中有节奏地给反馈,滚动加载、拖拽就该用它。

节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是:事件,按照一段时间的间隔来进行触发。

像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多

手写简版

使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行

时间戳方式:

// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
  // 上一次执行该函数的时间
  let lastTime = 0
  return function(...args) {
    // 当前时间
    let now = +new Date()
    // 将当前时间和上一次执行函数时间对比
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础进阶

    • 基础篇
    • 进阶篇
    • 高频篇
    • 手写篇
      • 1 实现防抖函数(debounce)
      • 2 实现节流函数(throttle)
      • 3 实现instanceOf
      • 4 实现new的过程
      • 5 实现call方法
      • 6 实现apply方法
      • 7 实现bind方法
      • 8 实现深拷贝
        • 1 简洁版本
        • 2 实现完整的深拷贝
      • 9 实现类的继承
      • 10 实现Promise相关方法
      • 11 实现发布订阅模式
      • 12 实现观察者模式
      • 13 实现单例模式
      • 14 实现Ajax
      • 15 实现JSONP方法
      • 16 实现async/await
      • 17 基于Generator函数实现async/await原理
      • 18 实现ES6的const
      • 19 实现一个迭代器生成函数
      • 20 实现ES6的extends
      • 21 实现Object.create
      • 22 实现Object.freeze
      • 23 实现Object.is
      • 24 实现一个compose函数
      • 25 setTimeout与setInterval实现
      • 26 实现Node的require方法
      • 27 实现LRU淘汰算法
      • 28 框架相关
      • 29 数组相关
      • 30 正则相关
      • 31 函数柯里化相关
      • 32 字符串相关
      • 33 实现工具函数
      • 34 手写常见排序
      • 35 算法数据结构
      • 36 综合
    • 设计模式
  • 精选模块

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

前端手写面试题大全:Promise、防抖节流、call、bind 与 new

# 面试高频手写题

手写题别急着背完整代码。先用一句话说清输入、输出和最容易踩的边界,再关掉答案自己写一遍;写完拿空值、重复调用和异步顺序试一试。面试时就算没写完,能把思路和取舍讲明白,也比默写一段记不住的实现更有帮助。

建议优先掌握:

  • instanceof - 考察对原型链的理解
  • new - 对创建对象实例过程的理解
  • call/apply/bind - 对this指向的理解
  • 手写promise - 对异步的理解
  • 手写原生ajax - 对ajax原理和http请求方式的理解,重点是get和post请求的实现

# 1 实现防抖函数(debounce)

30 秒速记

  • 防抖 = 电梯关门:有人进来就重新等,停止触发 wait 毫秒后才执行最后一次
  • 核心三步:clearTimeout(timer) → timer = setTimeout(...) → 到点 fn.apply(this, args)
  • 返回普通函数(不是箭头函数)才能拿到调用时的 this;参数用 ...args 传最后一次的
  • 进阶:immediate 首次立即执行、cancel() 取消、flush() 立刻执行挂起的那次
  • 场景:搜索联想、输入校验、resize 结束后重算;React 里要用 useMemo / useRef 保持同一个防抖实例

防抖就是事件连续触发时一直重新计时,停下来超过 wait 才执行一次。 像电梯关门,只要还有人进来就重新等。实现就是闭包里存一个 timer,每次调用先 clearTimeout,再开新的 setTimeout,到点用 fn.apply(this, args) 执行,这样 this 和最后一次的参数都对。和节流的区别是:防抖只要最后一次,持续触发就一直不执行;节流是按固定间隔执行。React 里有个常见坑,每次渲染都 debounce(fn) 出一个新函数,计时器各自独立,等于没防。

防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算

防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行

eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

手写简化版:

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
  // 缓存一个定时器id
  let timer = 0
  // 这里返回的函数是每次用户实际调用的防抖函数
  // 如果已经设定过定时器了就清空上一次的定时器
  // 开始一个新的定时器,延迟执行用户传入的方法
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

适用场景:

  • 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好
  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

面试时写出基础版之后,面试官多半会追问「第一次能不能立刻执行」「能不能取消」。下面这版把两点都补上,代码不长,建议能默写:

function debounce(fn, wait = 300, immediate = false) {
  let timer = null

  function debounced(...args) {
    // 立即执行版:当前没有计时器,说明是一串触发里的第一次
    const callNow = immediate && !timer
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      // 非立即版在这里执行最后一次
      if (!immediate) fn.apply(this, args)
    }, wait)
    if (callNow) return fn.apply(this, args)
  }

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

  return debounced
}

const log = debounce((v) => console.log('search', v), 300)
log('a'); log('ab'); log('abc')
// 300ms 后只输出一次:search abc

const submit = debounce(() => console.log('submit'), 1000, true)
submit(); submit(); submit()
// 立刻输出一次 submit,1 秒内后面两次被吞掉
@前端进阶之旅: 代码已经复制到剪贴板

几个容易丢分的点:返回的 debounced 必须是普通函数,箭头函数拿不到调用时的 this;定时器回调用箭头函数,才能沿用外层的 this 和 args;立即执行版要在定时器到点时把 timer 置空,否则下一轮第一次触发不会立即执行。在 React 里使用时,防抖实例要用 useMemo 或 useRef 固定住,组件卸载时调用 cancel(),避免卸载后还去 setState。

面试官追问

追问 1搜索框为什么用防抖不用节流?
参考回答

产品要的是用户停下来后只查最后一次。节流在输入过程中每隔一段时间就会发一次,中间那些请求都是浪费。防抖的代价是停顿前看不到中间结果,一般 300ms 左右体感就可以。

追问 2组件里写 const onSearch = debounce(fetchList, 300),结果每个字都发了请求,为什么?
参考回答

每次渲染都创建一个新的防抖函数,新函数清不掉旧函数的 timer,等于没防。用 useMemo(() => debounce(fetchList, 300), []) 或 useRef 保持同一个实例,卸载时调 cancel()。

追问 3提交按钮想防重复点击,但第一次点击要立刻有反应,怎么改?
参考回答

尾部防抖会让第一次点击延迟 wait 才执行。要用立即执行版:timer 为空时直接执行,然后开计时器,wait 内的点击只重置计时。不过防重复提交更靠谱的是请求期间禁用按钮,再加服务端幂等。

追问 4防抖后的函数有返回值,调用方拿得到吗?
参考回答

尾部执行版拿不到,原函数是在定时器里执行的,调用时它还没跑。立即执行版可以同步返回;要拿尾部那次的结果,就让防抖函数返回一个 Promise,到点后 resolve。

追问 5搜索联想加了防抖,偶尔还是显示上一个关键词的结果,为什么?
参考回答

防抖只减少请求数,管不了返回顺序。前一个请求慢、后一个快,旧结果后到就把新结果盖了。每次请求带上序号,返回时不是最新的就丢弃,或者用 AbortController 取消上一个请求。

# 2 实现节流函数(throttle)

30 秒速记

  • 节流 = 水龙头限流:持续触发时每隔 wait 最多执行一次,不会一直拖着不执行
  • 时间戳版:now - last >= wait 才执行,首次立即执行,但停止前的最后一次可能丢
  • 定时器版:没有挂起任务时才开 setTimeout,首次要等 wait,停止后还会补一次
  • 完整版两者结合:首次立即、末尾补一次(lodash 的 leading / trailing)
  • 场景:滚动加载、拖拽、resize、埋点上报;和视觉动画相关的优先考虑 requestAnimationFrame

节流是不管事件触发多频繁,每隔一段时间最多只执行一次。 有两种写法:时间戳版记录上次执行时间,间隔够了就执行,所以第一次立刻响应,但停止触发前最后那次如果没到间隔就丢了;定时器版是没有挂起任务时才开一个 setTimeout,第一次要等,停下后还会补执行一次。拖拽这种既要跟手又要落到终点的场景,单用哪种都不完整,得把两种结合起来。和防抖比,节流能在持续触发的过程中有节奏地给反馈,滚动加载、拖拽就该用它。

← 高频篇设计模式 →

fe