前端10道面试题

80 阅读12分钟

第二十三关:微任务 vs 宏任务(深度设计意图)

如果你是浏览器的设计师,你也会这么干。咱们用**“大白话”**把这个设计逻辑理顺:

1. 宏任务(MacroTask):就像是“重新排队”

  • 代表setTimeout, setInterval, I/O
  • 特点:它们通常是浏览器层面的操作,或者是比较“重”的任务。一旦丢进宏任务队列,就意味着你要去队尾重新排队。

2. 微任务(MicroTask):就像是“顺便处理”

  • 代表Promise.then, MutationObserver
  • 特点:它们通常是当前这段代码执行完后,立刻产生的后续动作。

3. 为什么微任务优先级高?(核心原因)

原因 A:为了“尽快响应”状态变化。 想象你在写一个复杂的业务逻辑。当你把 Promise 的状态从“等待”变为“成功”时,你肯定希望接下来的 .then() 里的代码马上跑起来,而不是去宏任务队列里排在那堆 100 毫秒后的定时器后面。如果还要重新排队,页面的响应就会有明显的“延迟感”。

原因 B:为了减少“渲染次数”,提高性能。 这是最关键的技术点!

  • 浏览器的渲染(更新界面)是在一个事件循环周期结束时发生的。
  • 如果微任务能在渲染之前全部执行完,那么微任务里对 DOM 的所有修改,都会在这一次渲染中合并完成
  • 如果微任务变成了宏任务,那么每改一次 DOM 可能就要触发一次渲染,浏览器会累死的。

第二十四关:垃圾回收的“内存泄漏”实战

刚才聊了 GC(垃圾回收),面试官通常会问你具体的“翻车现场”。

面试题: 请说出在 JavaScript 开发中,哪几种写法最容易导致内存泄漏(即:变量没用了,但 GC 不敢收它)?


💡 提示(不专业的“大白话”):

  1. 被遗忘的“传家宝” :全局变量。
  2. 停不下来的“闹钟” :定时器忘了关。
  3. 断不开的“线” :闭包使用不当。
  4. 已经消失的“影子” :DOM 节点删了,但 JS 变量还引用着它。

1. 关于定时器(遗忘的闹钟)

  • 深度点:并不是 setTimeout 只要不清除就会一直漏。如果是 setTimeout 且只执行一次,执行完后它其实会被回收。
  • 真凶:最危险的是 setInterval。只要你不清,它引用的外部变量就永远是“可达”的,GC 永远不敢动。

2. 关于闭包(断不开的线)

  • 深度点:你可以提到 V8 引擎的优化。现在的 V8 比较聪明,如果闭包里没用到某个大对象,它会自动帮你释放。
  • 场景:但如果多个内部函数共享一个作用域,其中一个用了大对象,其他函数也会导致这个大对象被留在内存里。

3. 关于脱离文档的 DOM(消失的影子)

  • 深度点:这个最隐蔽。
  • 例子:你在 JS 里写了 const myBtn = document.getElementById('btn')。后来你用 removeChild 把这个按钮删了,但在 JS 变量里 myBtn 还指着它。
  • 结果:这个 DOM 节点就成了“孤儿”,它在页面上看不见,但在内存里还占着地儿。

1. 全局变量:为什么它是“钉子户”?

大白话解释: 在 JS 里,全局变量(比如你直接写 var a = 1 或者挂在 window 上的属性)就像是**“皇亲国戚”**。 垃圾回收机制(GC)在清理内存时,会从根部(Root/Window)开始找。因为全局变量直接长在根部,所以它永远是“可达”的。

  • 危险点: 只要你的网页不关闭,全局变量占用的内存就永远不会被释放
  • 面试避坑话术: “为了避免内存泄漏,我会尽量减少全局变量的使用。如果必须用,我会在不使用它的时候手动给它赋值为 null,告诉 GC 这个人可以‘清理’了。”

第二十六关:异步进阶之 Async/Await 的本质

既然聊到了 JS 的运行,面试官经常会问这道题来考察你对 ES6 语法的理解深度:

面试题: async/await 听起来很高级,但它本质上是什么东西的“语法糖”?如果让你不用 async/await,只用 Generator(生成器)Promise,你能实现同样的效果吗?

不知道

1. 什么是“语法糖”?

大白话:就像是把苦药包了层糖衣

  • 内部逻辑还是那一套(苦药),但给你提供了一套更漂亮、更好写、更不容易出错的写法(糖衣)。
  • async/await 的糖衣下,包裹的其实是 Generator(生成器)

2. 它本质是怎么工作的?(三步走)

我们可以把 async/await 想象成一个**“自动排队执行器”**:

  1. 暂停(Yield) :当代码运行到 await 时,它会像按下“暂停键”一样,把当前的函数执行停住,跳出去干别的事。
  2. 等待(Promise) :它会等 await 后面的那个异步任务(Promise)出结果。
  3. 恢复(Next) :一旦结果回来了,它会自动点一下“继续播放”,把结果塞回刚才暂停的地方,接着往下走。

所以说:async/await = Generator + Promise + 自动执行器

第二十八关:判断对象相等(引用 vs 内容)

面试题: 在 JS 中,const a = { x: 1 }; const b = { x: 1 };

  1. 请问 a == ba === b 返回什么?
  2. 如果我非要判断它们“长得一样”(即属性和值都相同),你有什么办法?

💡 不专业的“大白话”提示:

  1. 引用的陷阱

    • 两个对象就像两对双胞胎,虽然长得一模一样,但他们是两个独立的人(占用不同的内存地址)。
    • 所以 === 比较的是“是不是同一个人”,结果一定是 false
  2. 如何判断“长得一样”?(深度比较)

    • 偷懒法JSON.stringify(a) === JSON.stringify(b)

      • :如果属性顺序不一样(比如 {x:1, y:2}{y:2, x:1}),这个方法就失效了。
    • 递归法:写个函数,把里面的属性一个个掏出来对比。如果属性又是对象,就接着往深了掏(递归)。

请说出至少 3 种水平垂直居中的方法。(这是前端面试的“开门红”题,必背)。

第一个方法是用 display: flex,然后加上 align-items: center 和 justify-content: center; 第二个方法是通过绝对定位,设置 position: absolute,top: 50%,left: 50%,再用 transform: translate(-50%, -50%); 第三个方法是用 display: grid(你说的新出的grid),然后用 place-items: center。

居中方案深度复盘

  1. Flexbox(最推荐)

    • 评价:现代开发的“标准答案”。简单、好记,几乎能解决 90% 的布局问题。
    • 细节:记得提到它是给父元素设置的。
  2. 绝对定位 + Transform(最经典)

    • 评价:如果你需要兼容一些老项目,或者元素需要脱离文档流,这是必杀技。
    • 深度点:为什么要用 translate(-50%, -50%)?因为 top: 50% 是让元素的左上角居中,而 translate 是相对于元素自身的长宽往回拉一半,这样中心点才真正重合。
  3. Grid 布局(最优雅)

    • 评价:代码量最少,只有两行。
    • 细节:它是 CSS 布局的终极方案,适合更复杂的二维网格。

第三十一关:如何处理 JS 的浮点数精度问题?

这是一个非常有名的“JS 坑”,也经常作为面试的趣味题。

面试题:

为什么在 JS 里 0.1 + 0.2 不等于 0.3?(它等于 0.30000000000000004)。

如果你在做一个电商网站,需要算商品总价,你怎么解决这个问题?


💡 不专业的“大白话”提示:

  1. 原因(进制转换)

    • 电脑是用二进制(0和1)干活的。有些十进制的小数(比如 0.1 和 0.2)在转成二进制时,就像 1÷31 \div 3 一样,是无限循环的。
    • JS 存储数字的长度有限,只能把后面的尾巴截掉,这一截,精度就丢了。
  2. 解决方法

    • 法一(最常用) :先变成整数。比如 0.1+0.2(0.1×10+0.2×10)÷100.1 + 0.2 \rightarrow (0.1 \times 10 + 0.2 \times 10) \div 10
    • 法二:使用官方提供的 toFixed(2) 保留两位小数,或者用专门处理大数的第三方库(如 Big.js)。

第三十二关:DOM 操作——重绘(Repaint)与 重排(Reflow)

这是衡量一个前端是否懂“浏览器性能渲染”的标准。

面试题:

  1. 什么是重排(也叫回流)?什么是重绘
  2. 哪一个对性能的影响更大?
  3. 你在写代码时,怎么减少它们的发生?

💡 提示(不专业的“大白话”):

  • 重排:动了骨架。比如改了宽高、位置。浏览器得重新算整个页面的布局,非常累。
  • 重绘:动了皮毛。比如改了颜色、背景。浏览器只需要重新涂色,比较轻松。

第三十三关:深挖 ES6+ 新语法(可选链与空值合并)

这两个语法虽然简单,但能极大提升代码的健壮性

面试题:

  1. 什么是可选链(Optional Chaining) ?.?它解决了什么痛点?
  2. 什么是空值合并运算符 ???它和逻辑或 || 有什么区别?

💡 不专业的“大白话”提示:

  1. 可选链 ?.

    • 痛点:以前如果你要访问 user.address.street,但万一 user 或是 addressundefined,代码直接报错崩溃(这就是臭名昭著的 Cannot read property of undefined)。
    • 大白话:加上问号,意思是:“嘿,哥们,如果你存在就继续往下找,如果你是空的,就直接停下给我个 undefined,别报错。”
  2. 空值合并 ??

    • 区别|| 很霸道,如果你设置的值是 0 或者 ""(空字符串),它也会认为你是“假的”,强行给你赋个默认值。
    • 大白话?? 只在你是 nullundefined 的时候才生效。如果你就是想存个 0,它会尊重你。

第三十四关:设计模式之“观察者模式” vs “发布订阅模式”

面试官如果想看你的架构思维,这题必考。

面试题:

  1. 什么是发布订阅模式?你能举一个前端开发中的例子吗?
  2. Vue 的数据响应式原理,或者是你用的事件总线(EventBus),属于哪种模式?

💡 不专业的“大白话”提示:

  • 例子:就像你在微信公众号点了“关注”。
  • 角色:公众号作者(发布者)一发文章,所有关注的人(订阅者)都会收到推送。你不需要天天刷新页面看他发没发,等着就行。
  • 好处:解耦。作者不需要知道每个粉丝是谁,粉丝也不需要盯着作者看。 观察者模式” vs “发布订阅模式 有什么区别

这个问题非常经典,很多开发者会把它们混为一谈。虽然它们的核心思想都是“当一个事情发生时,通知其他人”,但它们的实现结构完全不同。

最直白的区别在于:有没有“中间商”?


1. 观察者模式(Observer Pattern)—— “直连模式”

比喻:  就像老师在教室里点名

  • 主体(Subject) :老师。

  • 观察者(Observer) :学生。

  • 过程:老师手里拿着名单,挨个喊名字。老师认识每一个学生,学生也直接听老师的话。

  • 特点

    • 互相认识:主体维护着观察者的列表。
    • 同步耦合:主体直接调用观察者的方法。

2. 发布订阅模式(Pub-Sub Pattern)—— “平台模式”

比喻:  就像我们在淘宝买东西

  • 发布者(Publisher) :卖家(只管上架商品,不认识买家是谁)。

  • 订阅者(Subscriber) :买家(在后台点“开售提醒”,不认识卖家是谁)。

  • 调度中心(Broker/Event Bus) :淘宝平台。

  • 过程:卖家告诉淘宝“货到了”,淘宝查一下谁订了,然后发短信通知买家。

  • 特点

    • 互不认识:发布者和订阅者完全解耦,它们只跟“中间平台”说话。
    • 异步灵活:中间商可以对消息进行过滤、排序或延迟发送。

3. 核心区别对照表

特性观察者模式发布订阅模式
中间媒介(直接通信)(事件调度中心/Broker)
耦合度中度(主体知道观察者的存在)极低(双方完全不知道对方存在)
适用场景内部状态同步(如 Vue2 的响应式)跨组件/跨模块通信(如 EventBus)
代码表现subject.addObserver(ob)bus.on('event', cb) / bus.emit('event')

💡 面试加分回答:

“在 Vue2 内部实现数据响应式时,使用的是观察者模式(Dep 是主体,Watcher 是观察者);而我们在项目中常用的 EventBus 或者 Node.js 的 EventEmitter,则是标准的发布订阅模式。”

第三十六关:闭包的真正用途(封装与私有化)

面试题: 除了能延长变量寿命,闭包在实际开发中最大的用途是什么?如何用它实现私有变量

💡 不专业的“大白话”提示:

  • 私有变量:JS 以前没有 private 关键字(虽然现在有了 #)。如果你想定义一个变量,让别人只能看不能直接改,或者只能通过你允许的方法改,闭包就是唯一的“防盗门”。
  • 例子:想象一个银行账户,余额 money 不能让人直接 account.money = 1000000,你得提供个 deposit()(存款)方法。

第三十七关:MapObject 的区别

这是考察你对 ES6 数据结构 掌握程度的题目。

面试题: 既然我们已经有了对象 {},为什么 ES6 还要出个 Map?它们两个有什么区别?

💡 提示(大白话对比):

  1. 键(Key)的类型

    • Object:很死板,键只能是 字符串 或者 Symbol。如果你用个数字或对象当键,它会偷偷帮你转成字符串。
    • Map:非常奔放,任何东西都能当键(对象、函数、数字,统统没问题)。
  2. 顺序

    • Object:存进去的属性顺序通常不固定(尤其是数字键)。
    • Map:严格按照你存进去的顺序排列,遍历起来非常听话。
  3. 大小

    • Object:想知道存了多少个,得手动 Object.keys().length
    • Map:自带 size 属性,一秒告诉你结果。

第三十八关:什么是 DOM 的“事件冒泡”与“事件捕获”?

这题是事件委派(我们之前聊过)的底层逻辑。

面试题:

  1. 当你点击页面上的一个按钮时,事件流的顺序是怎样的?
  2. 什么是冒泡?什么是捕获
  3. 怎么阻止事件继续往上传递?

💡 提示(大白话):

  • 捕获(Capture) :从外往里。就像警察抓犯人,先包围整栋楼,再进房间。
  • 冒泡(Bubbling) :从里往外。就像鱼吐泡泡,从水底升到水面。
  • 阻止方法e.stopPropagation()