前端的面试笔记——HTML&JavaScript篇(一)

发布于:2025-05-20 ⋅ 阅读:(14) ⋅ 点赞:(0)

1、页面渲染过程

页面渲染过程是浏览器将获取到的 HTML、CSS、JavaScript 等资源转换为可视化页面的核心流程。以下从浏览器的视角,分阶段详细解析这一过程,并结合性能优化要点进行说明:

一、整体流程概览

1.资源加载 → 2. 构建渲染树 → 3. 布局计算(回流) → 4. 绘制(重绘) → 5. 合成层处理
其中,JavaScript 的执行可能穿插在各个阶段,影响渲染效率。

二、分阶段详细解析

1. 资源加载阶段

  • 浏览器接收 HTML
    浏览器通过网络请求获取 HTML 文档,开始解析过程。若遇到 <link>(CSS)<script>(JS)<img> 等标 签,会异步加载对应资源(默认情况下,CSS 不阻塞 HTML 解析,但 JS 会阻塞解析,除非标记为 async defer)。
  • 关键资源与阻塞行为
    • 阻塞渲染的资源:
      • 同步加载的 JS(<script>):解析到 JS 时,会暂停 HTML 解析,优先下载并执行 JS(因 JS 可能修改 DOM/CSSOM)。
      • CSS:虽不阻塞 HTML 解析,但会阻塞渲染(需等待 CSSOM 构建完成才能生成渲染树)。
    • 非阻塞资源:
      • 异步 JS(<script async>):下载时不阻塞解析,加载完成后立即执行(可能打断 HTML 解析)。
      • 延迟 JS(<script defer>):下载时不阻塞解析,等待 HTML 解析完成后按顺序执行。
    • 预加载优化:
      使用 <link rel="preload"> 强制预加载关键资源(如首屏字体、异步组件代码),减少后续加载延迟。
  • 事件触发
    • DOMContentLoaded:当 HTML 解析完成(不等待 CSS/JS 加载完成),触发该事件。
    • load: 当所有资源(图片、CSS、JS 等)加载完成后触发。

2. 构建渲染树(关键阶段)

  • 构建 DOM 树
    浏览器将 HTML 字符串解析为 DOM 节点树(Document Object Model),反映页面的结构(如 <div>, <p>等元素)。

  • 构建 CSSOM(CSS 对象模型)
    解析 CSS 文件(包括 <style> 标签和外部 CSS),生成 CSSOM 树,表示元素的样式规则。CSSOM 是只读的,JS 可通过 getComputedStyle 访问,但修改样式会触发重新计算。

  • 合成渲染树(Render Tree)
    DOM 树和 CSSOM 树合并,生成 渲染树,仅包含可见元素(display: none 的元素会被排除,visibility: hidden 的元素仍会参与布局和绘制)。

    渲染树的每个节点(渲染对象)包含元素的标签、样式、位置及大小等信息,用于后续布局和绘制。

3. 布局计算(回流 / Reflow)

  • 确定元素几何信息
    渲染树生成后,浏览器计算每个元素在视口中的 位置和大小(布局),称为 回流(Reflow)
    • 影响布局的属性:width, height, padding, margin, left, top, border 等。
    • 回流会递归影响其子元素及祖先元素,计算成本较高(尤其是复杂页面)。
  • 布局优化
    • 避免频繁操作布局属性(如多次读取 offsetWidth),可通过缓存值或批量修改样式(如使用 CSS 类一次性更新多个样式)减少回流次数。
    • 利用 requestAnimationFrame 延迟布局计算,与浏览器刷新频率同步(60Hz 下约 16ms 一次)。

4. 绘制(重绘 / Repaint)

  • 像素级渲染
    在布局完成后,浏览器根据渲染树的样式信息(颜色、背景、边框、阴影等),将元素绘制到 图层(Layer) 上,称为 重绘(Repaint)。
    • 重绘无需重新计算布局,但需重新绘制受影响的像素(如修改 color, background 等不影响布局的属性)。
    • 重绘范围通常小于回流,但仍有性能开销。
  • 绘制优化
    减少绘制区域:通过 will-changetransform/opacity 等合成属性,将元素提升为独立合成层,限制重绘范围。

5. 合成层处理(Composite)

  • 图层合成
    浏览器将页面划分为多个 合成层(Layer,如 3D 变换元素、视频、溢出滚动容器等),每个层独立绘制后,按层级顺序合并(Composite)到最终的屏幕图像。
  • 浏览器刷新机制
    合成后的图层通过 GPU 加速 输出到屏幕,与显示器的垂直同步信号(VSync)同步,确保流畅的 60FPS 渲染(每帧约 16ms)。

三、首次渲染(首屏渲染)的关键路径

  1. **关键资源:**首屏渲染依赖的 HTML、CSS、JS、字体、图片等资源。
  2. 阻塞点:
    • JS 执行阻塞 HTML 解析和渲染(尤其是同步 JS)。
    • CSS 未加载完成时,渲染会被阻塞(浏览器需等待 CSSOM 构建完成才能生成渲染树)。
  3. 优化方向:
    • 减少关键资源:删除无用 CSS/JS,使用 Tree-shaking、Code Splitting(如异步组件)。
    • 优化加载顺序:
      • 内联首屏必需的 CSS(避免阻塞渲染),异步加载非首屏 CSS。
      • 将非关键 JS 标记为 defer async,或使用动态导入(import())。
    • 缩短关键路径:
      通过服务端渲染(SSR)预渲染(Prerender) 提前生成 HTML,减少客户端渲染压力。

四、回流与重绘的触发场景

操作类型 触发回流(Reflow) 触发重绘Repaint
修改布局属性 ✅(如 width, margin ✅(布局变化后需重绘)
修改绘制属性 ✅(如 color, box-shadow
读取布局/绘制属性 ✅(如 offsetHeight, getBoundingClientRect ❌(仅读取时可能触发回流 / 重绘)
添加/删除 DOM 元素 ✅(可能影响布局) ✅(元素显示变化)
修改合成属性(transform/opacity ❌(仅触发合成) ❌(仅触发合成,不影响布局 / 绘制)

五、性能优化核心原则

  1. 减少关键资源阻塞:
    • 异步加载非关键 JS/CSS,使用 <script defer>、动态导入、媒体查询加载 CSS(media="print" 不阻塞渲染)。
    • 预加载关键资源:<link rel="preload" href="关键JS.css" as="style">
  2. 简化渲染树:
    • 减少 DOM 层级深度(避免过多嵌套),移除无用的 display: none 元素(虽不参与渲染树,但增加 DOM 解析成本)。
  3. 避免频繁回流 / 重绘:
    • 批量修改样式(使用 CSS 类一次性更新多个属性),或通过 documentFragment 批量操作 DOM。
    • 将频繁动画的元素提升为合成层(will-change: transformtransform: translateZ(0))。
  4. 利用浏览器优化:
    • requestAnimationFrame:将回调函数绑定到浏览器刷新周期,减少布局 / 绘制次数。
    • content-visibility:对非首屏内容延迟渲染(如长列表分页),语法:content-visibility: auto;。

总结

页面渲染是浏览器解析、计算、绘制的复杂过程,核心优化点在于 缩短关键路径(减少阻塞资源)、降低渲染计算成本(避免不必要的回流 / 重绘)、合理利用合成层加速。开发者需结合具体场景(如首屏渲染、动画性能),针对性优化各阶段性能,确保用户获得流畅的视觉体验。

2、页面渲染过程中JavaScript的执行时机

在浏览器的页面渲染过程中,JavaScript 的执行时机HTML 解析DOM 构建样式计算渲染流程密切相关,且会直接影响页面的加载性能和用户体验。以下是详细的执行时机分析及关键概念:

一、浏览器渲染流程概览

  1. 解析 HTML:浏览器逐行解析 HTML,构建 DOM 树(Document Object Model)。
  2. 解析 CSS:解析样式资源(CSS/stylesheet),构建 CSSOM 树(CSS Object Model)。
  3. 合成渲染树:将 DOM 树和 CSSOM 树合并为 渲染树(Render Tree),仅包含可见元素。
  4. 布局(Layout):计算元素的几何位置(宽高、坐标等)。
  5. 绘制(Paint):将元素的视觉样式(颜色、阴影等)绘制到像素缓冲区。
  6. 合成(Composite):将多层渲染层合并,生成最终屏幕图像。

JavaScript 会阻塞 HTML 解析和渲染(除非显式设置异步加载),因此其执行时机至关重要。

二、JavaScript 执行时机的关键阶段

1. 同步脚本(无 async/defer)

  • 时机:
    浏览器解析 HTML 时遇到 <script> 标签,立即停止解析 HTML,下载并执行脚本,执行完毕后继续解析 HTML。
  • 影响:
    • 阻塞 DOM 构建和渲染,导致页面白屏或延迟渲染。
    • 若脚本依赖 DOM 元素,需确保脚本在目标元素之后加载,或使用 DOMContentLoaded 事件(见下文)。
  • 示例:
    <script src="sync-script.js"></script> <!-- 同步执行,阻塞后续解析 -->
    

2. defer 脚本(延迟执行)

  • 时机:
    • 浏览器异步下载脚本(不阻塞 HTML 解析),仅在 DOM 解析完成后(DOMContentLoaded 事件前)按顺序执行。
    • 多个带 defer的脚本按标签顺序执行。
  • 适用场景:
    • 脚本依赖 DOM 结构,但不依赖其他异步资源(如图片、字体)。
    • 需保证脚本执行顺序(如多个有依赖关系的库)。
  • 适用场景:
    <script defer src="defer-script1.js"></script>
    <script defer src="defer-script2.js"></script> <!-- 按顺序执行 -->
    

3. async 脚本(异步执行)

  • 时机:
    • 浏览器异步下载脚本,下载完成后立即执行(可能在 DOM 解析过程中或完成后)。
    • 执行顺序不确定(按下载完成先后执行,不保证标签顺序)。
  • 适用场景:
    • 非关键脚本(如分析工具、广告),不依赖 DOM 或其他脚本的执行顺序。
    • 避免阻塞渲染,但不保证执行顺序。
  • 示例:
    <script async src="async-script1.js"></script>
    <script async src="async-script2.js"></script> <!-- 执行顺序不确定 -->
    

4. DOMContentLoaded 事件

  • 触发时机:
    • 当 DOM 解析完成(无需等待 CSSOM 或资源加载),会触发 DOMContentLoaded 事件。
    • 此时可安全操作 DOM,但样式可能未完全计算(CSSOM 未构建完成时,部分样式可能未生效)。
  • 用途:
    • 将非阻塞脚本放在事件回调中,避免阻塞 HTML 解析。
    • 示例:
      document.addEventListener('DOMContentLoaded', () => {
      // 在此处操作 DOM 或执行脚本
      });
      

5. load 事件

  • 触发时机:
    • 当所有资源(HTML、CSS、JavaScript、图片、字体等)加载完成后触发。
  • 用途:
    • 处理依赖所有资源的逻辑(如统计页面完全加载时间)。
    • 示例:
      window.addEventListener('load', () => {
        // 所有资源加载完成后执行
      });
      

6. beforeunload 与 unload 事件

  • beforeunload:
    • 页面卸载前触发(如用户刷新或离开页面),可用于提示用户保存未提交的数据。
  • unload:
    • 页面卸载时触发(资源开始卸载),用于释放内存或取消未完成的请求,但操作有限(仅支持同步操作,且不保证执行完成)。

三、现代优化手段:动态导入与预加载

1. 动态导入(Dynamic Import)

  • 时机:
    • 使用import()动态加载模块,返回 Promise,仅在需要时执行(如路由跳转、事件触发时)。
  • 优势:
    • 实现代码分割,减少首屏加载的 JavaScript 体积,提升性能。
  • 示例:
    // 点击按钮时动态加载组件
    button.addEventListener('click', async () => {
      const module = await import('./dynamic-component.js');
      module.init();
    });
    

2. 预加载(Preload)

  • 时机:
    • 通过 <link rel="preload"> 告知浏览器提前下载资源(如关键脚本),但不立即执行。
  • 用途:
    • 提前加载后续需要的脚本,避免阻塞渲染时机。
  • 示例:
    <link rel="preload" href="critical-script.js" as="script">
    <script src="critical-script.js"></script> <!-- 已预加载,直接执行 -->
    

四、最佳实践:避免阻塞渲染

  1. 优先异步加载非关键脚本:
    • 对非必要脚本使用 defer async,避免阻塞 HTML 解析。
  2. 将同步脚本置于 <body> 底部:
    • 确保脚本在 DOM 元素之后加载,避免依赖未创建的元素。
  3. 使用 DOMContentLoaded 处理 DOM 依赖:
    • 非紧急的 DOM 操作放在 DOMContentLoaded 回调中,减少阻塞。
  4. 代码分割与动态导入:
    • 通过 Webpack、Rollup 等工具拆分代码,按需加载模块。
  5. 预加载关键资源:
    • 对首屏必需的脚本或字体使用 <link rel="preload"> 提前下载。

总结:执行时机与渲染的关系

脚本类型 是否阻塞HTML解析 执行时机 执行顺序
同步脚本 解析到标签时立即执行 顺序执行
defer 脚本 DOM 解析完成后(DOMContentLoaded 前) 顺序执行
async 脚本 下载完成后立即执行(可能早于或晚于 DOM 解析) 不保证顺序

合理控制 JavaScript 的执行时机,是优化页面性能、避免阻塞渲染的核心手段。通过异步加载、延迟执行和动态导入,可显著提升用户体验。


网站公告

今日签到

点亮在社区的每一天
去签到