第二十三关:微任务 vs 宏任务(深度设计意图)
如果你是浏览器的设计师,你也会这么干。咱们用**“大白话”**把这个设计逻辑理顺:
1. 宏任务(MacroTask):就像是“重新排队”
- 代表:
setTimeout,setInterval,I/O。 - 特点:它们通常是浏览器层面的操作,或者是比较“重”的任务。一旦丢进宏任务队列,就意味着你要去队尾重新排队。
2. 微任务(MicroTask):就像是“顺便处理”
- 代表:
Promise.then,MutationObserver。 - 特点:它们通常是当前这段代码执行完后,立刻产生的后续动作。
3. 为什么微任务优先级高?(核心原因)
原因 A:为了“尽快响应”状态变化。 想象你在写一个复杂的业务逻辑。当你把 Promise 的状态从“等待”变为“成功”时,你肯定希望接下来的 .then() 里的代码马上跑起来,而不是去宏任务队列里排在那堆 100 毫秒后的定时器后面。如果还要重新排队,页面的响应就会有明显的“延迟感”。
原因 B:为了减少“渲染次数”,提高性能。 这是最关键的技术点!
- 浏览器的渲染(更新界面)是在一个事件循环周期结束时发生的。
- 如果微任务能在渲染之前全部执行完,那么微任务里对 DOM 的所有修改,都会在这一次渲染中合并完成。
- 如果微任务变成了宏任务,那么每改一次 DOM 可能就要触发一次渲染,浏览器会累死的。
第二十四关:垃圾回收的“内存泄漏”实战
刚才聊了 GC(垃圾回收),面试官通常会问你具体的“翻车现场”。
面试题: 请说出在 JavaScript 开发中,哪几种写法最容易导致内存泄漏(即:变量没用了,但 GC 不敢收它)?
💡 提示(不专业的“大白话”):
- 被遗忘的“传家宝” :全局变量。
- 停不下来的“闹钟” :定时器忘了关。
- 断不开的“线” :闭包使用不当。
- 已经消失的“影子” :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 想象成一个**“自动排队执行器”**:
- 暂停(Yield) :当代码运行到
await时,它会像按下“暂停键”一样,把当前的函数执行停住,跳出去干别的事。 - 等待(Promise) :它会等
await后面的那个异步任务(Promise)出结果。 - 恢复(Next) :一旦结果回来了,它会自动点一下“继续播放”,把结果塞回刚才暂停的地方,接着往下走。
所以说:async/await = Generator + Promise + 自动执行器。
第二十八关:判断对象相等(引用 vs 内容)
面试题: 在 JS 中,const a = { x: 1 }; const b = { x: 1 };。
- 请问
a == b或a === b返回什么? - 如果我非要判断它们“长得一样”(即属性和值都相同),你有什么办法?
💡 不专业的“大白话”提示:
-
引用的陷阱:
- 两个对象就像两对双胞胎,虽然长得一模一样,但他们是两个独立的人(占用不同的内存地址)。
- 所以
===比较的是“是不是同一个人”,结果一定是false。
-
如何判断“长得一样”?(深度比较) :
-
偷懒法:
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。
居中方案深度复盘
-
Flexbox(最推荐) :
- 评价:现代开发的“标准答案”。简单、好记,几乎能解决 90% 的布局问题。
- 细节:记得提到它是给父元素设置的。
-
绝对定位 + Transform(最经典) :
- 评价:如果你需要兼容一些老项目,或者元素需要脱离文档流,这是必杀技。
- 深度点:为什么要用
translate(-50%, -50%)?因为top: 50%是让元素的左上角居中,而translate是相对于元素自身的长宽往回拉一半,这样中心点才真正重合。
-
Grid 布局(最优雅) :
- 评价:代码量最少,只有两行。
- 细节:它是 CSS 布局的终极方案,适合更复杂的二维网格。
第三十一关:如何处理 JS 的浮点数精度问题?
这是一个非常有名的“JS 坑”,也经常作为面试的趣味题。
面试题:
为什么在 JS 里 0.1 + 0.2 不等于 0.3?(它等于 0.30000000000000004)。
如果你在做一个电商网站,需要算商品总价,你怎么解决这个问题?
💡 不专业的“大白话”提示:
-
原因(进制转换) :
- 电脑是用二进制(0和1)干活的。有些十进制的小数(比如 0.1 和 0.2)在转成二进制时,就像 一样,是无限循环的。
- JS 存储数字的长度有限,只能把后面的尾巴截掉,这一截,精度就丢了。
-
解决方法:
- 法一(最常用) :先变成整数。比如 。
- 法二:使用官方提供的
toFixed(2)保留两位小数,或者用专门处理大数的第三方库(如Big.js)。
第三十二关:DOM 操作——重绘(Repaint)与 重排(Reflow)
这是衡量一个前端是否懂“浏览器性能渲染”的标准。
面试题:
- 什么是重排(也叫回流)?什么是重绘?
- 哪一个对性能的影响更大?
- 你在写代码时,怎么减少它们的发生?
💡 提示(不专业的“大白话”):
- 重排:动了骨架。比如改了宽高、位置。浏览器得重新算整个页面的布局,非常累。
- 重绘:动了皮毛。比如改了颜色、背景。浏览器只需要重新涂色,比较轻松。
第三十三关:深挖 ES6+ 新语法(可选链与空值合并)
这两个语法虽然简单,但能极大提升代码的健壮性。
面试题:
- 什么是可选链(Optional Chaining)
?.?它解决了什么痛点? - 什么是空值合并运算符
???它和逻辑或||有什么区别?
💡 不专业的“大白话”提示:
-
可选链
?.:- 痛点:以前如果你要访问
user.address.street,但万一user或是address是undefined,代码直接报错崩溃(这就是臭名昭著的 Cannot read property of undefined)。 - 大白话:加上问号,意思是:“嘿,哥们,如果你存在就继续往下找,如果你是空的,就直接停下给我个
undefined,别报错。”
- 痛点:以前如果你要访问
-
空值合并
??:- 区别:
||很霸道,如果你设置的值是0或者""(空字符串),它也会认为你是“假的”,强行给你赋个默认值。 - 大白话:
??只在你是null或undefined的时候才生效。如果你就是想存个0,它会尊重你。
- 区别:
第三十四关:设计模式之“观察者模式” vs “发布订阅模式”
面试官如果想看你的架构思维,这题必考。
面试题:
- 什么是发布订阅模式?你能举一个前端开发中的例子吗?
- 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()(存款)方法。
第三十七关:Map 与 Object 的区别
这是考察你对 ES6 数据结构 掌握程度的题目。
面试题: 既然我们已经有了对象 {},为什么 ES6 还要出个 Map?它们两个有什么区别?
💡 提示(大白话对比):
-
键(Key)的类型:
- Object:很死板,键只能是 字符串 或者 Symbol。如果你用个数字或对象当键,它会偷偷帮你转成字符串。
- Map:非常奔放,任何东西都能当键(对象、函数、数字,统统没问题)。
-
顺序:
- Object:存进去的属性顺序通常不固定(尤其是数字键)。
- Map:严格按照你存进去的顺序排列,遍历起来非常听话。
-
大小:
- Object:想知道存了多少个,得手动
Object.keys().length。 - Map:自带
size属性,一秒告诉你结果。
- Object:想知道存了多少个,得手动
第三十八关:什么是 DOM 的“事件冒泡”与“事件捕获”?
这题是事件委派(我们之前聊过)的底层逻辑。
面试题:
- 当你点击页面上的一个按钮时,事件流的顺序是怎样的?
- 什么是冒泡?什么是捕获?
- 怎么阻止事件继续往上传递?
💡 提示(大白话):
- 捕获(Capture) :从外往里。就像警察抓犯人,先包围整栋楼,再进房间。
- 冒泡(Bubbling) :从里往外。就像鱼吐泡泡,从水底升到水面。
- 阻止方法:
e.stopPropagation()。