阶段 1:JS & 浏览器底层 - 核心重点整理

3 阅读4分钟

阶段 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 基于内容,更精确)


三、学习建议

  1. 手写是检验理解的唯一标准(手写 Promise、手写 Event Bus、手写深拷贝)
  2. 浏览器原理要通过打 Log 验证(在重排/重绘的地方加 console,或用 Performance 面板)
  3. Event Loop 必须能把题做对,而且是复杂的嵌套题
  4. 看完这部分,可以去做字节、阿里的面试题,基本能看出差距