浏览器原理

B 级 · 中频考点 · 渲染流水线完整版详见 网易腹稿 · 浏览器渲染

1 渲染流水线

从输入 URL 到页面显示的全过程?
① URL 解析 → ② DNS 解析 → ③ TCP 三次握手 → ④ TLS 握手(HTTPS)→ ⑤ 发送 HTTP 请求 → ⑥ 服务器处理并响应 → ⑦ 浏览器解析 HTML 构建 DOM → ⑧ 解析 CSS 构建 CSSOM → ⑨ 合成 Render Tree → ⑩ Layout(布局)→ ⑪ Paint(绘制)→ ⑫ Composite(合成)→ 屏幕显示

关键细节:

  • CSS 不阻塞 DOM 解析,但阻塞渲染(CSSOM 未完成不会绘制)
  • JS 阻塞 DOM 解析(除非 async/defer),因为 JS 可能修改 DOM
  • async:下载不阻塞,下载完立即执行(不保证顺序)
  • defer:下载不阻塞,DOMContentLoaded 前按序执行
重排(Reflow)vs 重绘(Repaint)?
重排:元素的几何属性(宽高、位置、边距)变化 → 重新计算布局 → 代价大。
重绘:外观改变但不影响布局(颜色、背景)→ 只需重新绘制 → 代价小。
重排一定触发重绘,重绘不一定触发重排。

减少重排:① 批量修改 DOM(DocumentFragment / display:none)② 用 transform 代替 top/left ③ 避免频繁读取布局属性(offsetHeight 等会强制同步布局)

2 事件机制

事件冒泡 / 捕获 / 委托?
DOM 事件流三阶段:捕获(从 window → 目标)→ 目标冒泡(从目标 → window)。
addEventListener(event, fn, true) 第三个参数为 true 监听捕获阶段,默认 false 监听冒泡阶段。

事件委托:利用冒泡,在父元素上监听子元素事件。优点:① 减少事件绑定数量 ② 动态添加的子元素也能响应。

document.getElementById('list').addEventListener('click', e => {
  if (e.target.tagName === 'LI') {
    console.log(e.target.textContent);
  }
});
stopPropagation vs preventDefault?
stopPropagation():阻止事件继续冒泡/捕获(不传给父元素)。
preventDefault():阻止浏览器默认行为(如链接跳转、表单提交),事件仍会冒泡。

3 存储

Cookie / localStorage / sessionStorage / IndexedDB 对比?
Cookie:~4KB,自动随请求发送,有过期时间,受同源策略和 SameSite 限制。
localStorage:~5-10MB,持久存储,不自动发送。
sessionStorage:~5-10MB,标签页关闭即清除,不跨标签页共享
IndexedDB:大容量(数百 MB),异步 API,支持索引和事务,适合离线应用。

4 安全

XSS 攻击是什么?怎么防?
XSS(跨站脚本攻击):攻击者在页面注入恶意脚本,窃取用户数据。
存储型:恶意代码存入数据库(如评论区)→ 其他用户浏览时执行。
反射型:恶意代码在 URL 参数中 → 服务端直接返回到页面。
DOM 型:纯前端,通过修改 DOM(innerHTML 等)注入。

防御:

  • 输出时做HTML 编码(转义 < > & " ')
  • 避免 innerHTML,用 textContent
  • 设置 Content-Security-Policy(CSP)限制脚本来源
  • Cookie 设 HttpOnly(JS 无法读取)
  • 使用框架自带的防 XSS 机制(React JSX 默认转义)
CSRF 攻击是什么?怎么防?
CSRF(跨站请求伪造):攻击者诱导用户访问恶意页面,利用用户已登录的 Cookie 发起伪造请求。

防御:

  • SameSite Cookie:设为 Strict 或 Lax,阻止跨站请求携带 Cookie
  • CSRF Token:服务端下发随机 token,每次请求必须携带验证
  • 检查 Origin/Referer:验证请求来源
  • 不用 GET 做状态变更

5 垃圾回收

V8 的垃圾回收机制?
V8 采用分代回收策略:
新生代(短命对象,~1-8MB):Scavenge 算法,将内存一分为二(From/To),存活对象从 From 复制到 To,然后交换。经过两次 Scavenge 仍存活的晋升到老生代。
老生代(长命对象):标记-清除(Mark-Sweep)+ 标记-整理(Mark-Compact)。标记阶段从根对象出发,标记所有可达对象;清除阶段回收未标记的;整理阶段消除内存碎片。

增量标记:将标记工作分成小块穿插在主线程中执行,避免长时间暂停(Stop-The-World)。

常见内存泄漏场景?
未清理的定时器/事件监听:组件卸载后 setInterval/addEventListener 仍在运行
闭包引用:闭包持有大对象的引用
全局变量:忘记 var/let/const 声明,变量挂到 window 上
DOM 引用:JS 中引用了已从 DOM 树移除的节点
console.log:开发工具打开时,log 的对象不会被回收