阶段 1:JS & 浏览器底层 - 核心重点整理
你说得对,这部分确实是很多人的短板。下面我给你梳理出最核心、最常考的知识点,以及需要达到的理解深度。
一、JS 核心
1. 闭包 / 作用域链
必须理解:
- 词法作用域(函数定义时决定,不是调用时)
- 闭包 = 函数 + 能访问外部函数作用域的能力
- 作用域链是单向的、逐级向上查找
// 经典闭包陷阱
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0) // 输出 3 3 3
}
// 解决方法:用 let 或 IIFE 创建新作用域
面试常问: 闭包的应用(模块化、防抖节流、函数柯里化)
2. 原型链
核心公式:
实例.__proto__ === 构造函数.prototype
构造函数.prototype.constructor === 构造函数
Object.__proto__ === Function.prototype // Function 是 Object 的实例
Function.prototype.__proto__ === Object.prototype
手写 instanceof:
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)
while (proto) {
if (proto === right.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
3. this 绑定(4 条规则,优先级从低到高)
| 绑定规则 | 条件 | this 指向 |
|---|---|---|
| 默认绑定 | 独立调用 | window / undefined(严格模式) |
| 隐式绑定 | 对象方法调用 | 调用对象 |
| 显式绑定 | call / apply / bind | 指定的对象 |
| new 绑定 | new 调用 | 新创建的实例 |
优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
// 箭头函数的 this 是词法作用域(继承外层函数的 this)
const obj = {
fn: () => {
console.log(this) // 外层 this(通常是 window)
}
}
4. Promise / async/await
手写重点:
- Promise 的三种状态(pending / fulfilled / rejected),一旦改变不可逆
- then 链式调用(返回新的 Promise)
- 错误冒泡和 catch
- Promise.all / race / allSettled / any
// 手写 Promise.all
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
let results = []
let count = 0
promises.forEach((p, i) => {
Promise.resolve(p).then(res => {
results[i] = res
count++
if (count === promises.length) resolve(results)
}).catch(reject)
})
})
}
5. 事件循环(Event Loop)⭐ 重中之重
宏任务 vs 微任务:
| 类型 | 常见 API |
|---|---|
| 宏任务 | setTimeout, setInterval, I/O, UI 渲染, MessageChannel |
| 微任务 | Promise.then, MutationObserver, queueMicrotask, process.nextTick(Node.js) |
执行流程:
1. 执行一个宏任务(最开始是 script 整体)
2. 执行过程中产生的微任务全部清空(按顺序)
3. 必要的话进行 UI 渲染(浏览器)
4. 取下一个宏任务执行,重复步骤 2-3
经典面试题:
console.log(1)
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
console.log(4)
// 输出:1 4 3 2
进阶:
async function async1() {
console.log(1)
await async2()
console.log(2) // await 后面的代码相当于 Promise.then
}
async function async2() { console.log(3) }
console.log(4)
setTimeout(() => console.log(5), 0)
async1()
new Promise(resolve => {
console.log(6)
resolve()
}).then(() => console.log(7))
console.log(8)
// 输出:4 1 3 6 8 2 7 5
二、浏览器原理
1. 渲染流程(关键渲染路径)
HTML → DOM 树
CSS → CSSOM 树
↓
Render Tree(排除 display: none、head、meta 等)
↓
Layout/Reflow(计算每个节点的位置和大小)
↓
Paint(绘制像素到屏幕上)
注意: visibility: hidden 会占据空间,display: none 不会
2. 重排 / 重绘
| 操作 | 触发 | 代价 |
|---|---|---|
| 重排(Reflow) | 修改尺寸、位置、内容、窗口大小、读取 offsetTop/clientWidth 等 | 高(可能导致子/父节点连锁重排) |
| 重绘(Repaint) | 修改颜色、背景色、visibility | 较低(不改变几何属性) |
避免重排的技巧:
- 使用 transform 代替 top/left(transform 不触发重排,只触发合成)
- 批量修改 DOM(使用 documentFragment)
- 读写分离(避免强制同步布局)
// 坏习惯
div.style.width = div.offsetWidth + 10 + 'px' // 先读后写,触发强制重排
// 好习惯
let width = div.offsetWidth // 批量读
div.style.width = width + 10 + 'px' // 批量写
3. 浏览器缓存机制
缓存位置优先级(从高到低):
Service Worker → Memory Cache → Disk Cache → Push Cache(HTTP/2)
HTTP 缓存(强缓存 + 协商缓存):
| 缓存类型 | 头部字段 | 行为 |
|---|---|---|
| 强缓存 | Cache-Control: max-age=3600, Expires | 缓存有效期内直接使用缓存,不发请求 |
| 协商缓存 | Last-Modified / If-Modified-Since, ETag / If-None-Match | 发请求验证,304 则使用缓存 |
Cache-Control 常用值:
public:任何节点都可以缓存private:只有浏览器可以缓存no-cache:不使用强缓存,每次都协商no-store:完全不缓存max-age=秒:强缓存有效期
ETag 优先级高于 Last-Modified(因为 ETag 基于内容,更精确)
三、学习建议
- 手写是检验理解的唯一标准(手写 Promise、手写 Event Bus、手写深拷贝)
- 浏览器原理要通过打 Log 验证(在重排/重绘的地方加 console,或用 Performance 面板)
- Event Loop 必须能把题做对,而且是复杂的嵌套题
- 看完这部分,可以去做字节、阿里的面试题,基本能看出差距