Skip to content

一文了解浏览器的渲染底层

1.强制重排:引例

在前端开发中,强制重排(reflow)是指浏览器重新计算元素的布局和几何属性,以确保在渲染页面或执行某些操作时元素的位置和大小是最新的。

一种常见的强制重排的技术是使用offsetWidth属性offsetWidth是一个只读属性,返回元素的像素宽度,包括元素的宽度、内边距和边框。当读取该属性时,浏览器被迫执行一次重排,以确保返回的值是最新的。

void wrapper.offsetWidth是一种常见的技巧,通过在offsetWidth前使用void操作符来避免返回的值对代码的其他部分产生副作用。这种技巧确保了只进行了重排操作而没有其他意外的副作用。

以下是一个示例展示了如何使用void wrapper.offsetWidth强制进行重排:

js
// 获取元素的宽度,并强制进行重排
void wrapper.offsetWidth;

// 在重排后进行一些操作
// ...

请注意,强制重排是一个相对较消耗性能的操作,特别是在某些情况下频繁地进行重排可能会导致性能问题。因此,应该避免滥用强制重排操作,只在必要的情况下使用,以尽量减少页面性能的影响。如果可能的话,可以通过优化布局和样式,以及使用其他技术来避免不必要的强制重排。

强制重排的作用?

注意:由于重拍必重绘,所以强制重排也相当于强制重绘了!

强制重排(reflow)在前端开发中有以下几个作用:

  1. 更新元素的布局和几何属性:通过强制重排,浏览器会重新计算元素的位置、大小和其他相关属性,以确保页面在渲染时的呈现是最新的。这对于响应式设计和动态布局尤为重要,可以确保元素按照预期的方式进行定位和渲染。
  2. 解决渲染问题:**当修改了元素的样式或内容后,浏览器可能需要重新计算并更新页面的渲染。**强制重排可以使浏览器重新计算并应用这些更改,确保页面得到正确的渲染。
  3. 获取准确的元素尺寸和位置信息:有时候,我们需要获取元素的实际尺寸或位置信息,例如计算元素的偏移量、检测元素是否可见等。在这种情况下,强制重排可以确保我们获取到的是最新的元素属性,而不是缓存的旧值。
  4. 优化动画和过渡效果:在实现动画和过渡效果时,我们通常希望元素的属性更改能够平滑地过渡。通过强制重排可以触发浏览器的重绘过程,帮助我们实现平滑的动画效果,避免不连续或闪烁的效果。

拿上面的例子来说:

js
wrapper.style.transition = 'none';
translateX = 0;
wrapper.style.transform = `translateX(${translateX}px)`;
void wrapper.offsetWidth; // 重点:强制重排(回流),立即将渲染页面
wrapper.style.transition = 'transform 0.3s ease-in-out'; //因为本身是有transition的,我们现在搞没了,所以需要手动设置回去!在这之前必须强制重排!

transform:translateX会造成重排,但是浏览器不会立即触发渲染(而是等待下一个渲染帧),但是我们后面又设置了transition = 'transform 0.3s ease-in-out';,下一次渲染帧就会根据这个transition渲染页面,所以前面的transition = 'none';就失效了,所以在这之前必须强制浏览器进行重排!

注意:当修改了元素的样式或内容后,浏览器并不会立即更新页面的渲染!

当修改了元素的样式或内容后,浏览器并不会立即更新页面的渲染(不会立即重绘和重排),而是将更新操作放入队列中,等待下一次渲染帧(rendering frame)来执行。

浏览器的渲染过程包括多个步骤,其中之一是重排(reflow)和重绘(repaint)。重排是指浏览器重新计算元素的布局和几何属性,而重绘是指浏览器根据最新的样式信息将元素的可视部分重新绘制到屏幕上。

因为重排和重绘是消耗性能的操作,浏览器会尽量将这些操作延迟执行,并进行优化。所以,在修改元素的样式或内容后,浏览器一般不会立即执行重排和重绘操作,而是将它们添加到一个队列中。然后,在下一次渲染帧到来时,浏览器会检查这个队列,并执行队列中需要重排和重绘的操作。

2.渲染帧(帧)

浏览器的渲染帧是什么?

在浏览器中,渲染帧(rendering frame)是指浏览器进行页面渲染的最小时间单位。浏览器以每秒约60帧(FPS)的速度进行渲染,也就是每秒钟会进行大约60次的渲染帧

渲染帧是浏览器进行渲染的基本单位,用于更新页面呈现、执行布局和绘制操作。每个渲染帧之间的时间间隔为约16毫秒(1000毫秒/60帧),这是为了尽可能平滑地实现动画效果。

在每个渲染帧中,浏览器会按照以下流程进行操作:(也就是浏览器的渲染过程)

  1. 处理用户输入:检查是否有用户的输入事件(比如鼠标移动、鼠标点击、键盘输入等),并对输入做出响应。

  2. 更新布局与样式:浏览器会通过解析HTML和CSS来确定页面的布局结构,并计算每个元素的大小、位置和样式。

  3. 执行 JavaScript:如果有JavaScript代码需要执行,浏览器会执行相应的脚本,处理事件、计算、操作DOM等。——> 注意:可以看出来js的任务是先于渲染任务执行的!

    比如用户可能会触发一些元素事件(输入文本点击按钮等),还可能有定时任务(会到下一次循环执行回调)或者异步任务执行。这里会进行js的事件循环!

  4. 重排(reflow)和重绘(repaint):根据最新的布局和样式信息,浏览器进行重排和重绘操作,更新页面的布局、几何属性和可视内容。

  5. 绘制页面:浏览器会将页面的可视内容绘制到屏幕上,包括绘制背景、文本、图像等。

  6. 提交页面:当渲染帧中的所有操作完成后,浏览器会将渲染好的页面提交给显示设备进行显示。

渲染帧的频率通常是固定的,但在某些情况下(例如电脑性能较差或浏览器资源受限),渲染帧的速率可能会下降。开发人员可以利用渲染帧的机制来实现平滑的动画效果,以及优化代码和样式,以提高页面的渲染性能。

js宏任务和浏览器渲染任务谁先执行?

一般情况下,宏任务先于渲染任务执行(在真正开始事件循环之后)。当主线程空闲时,事件循环会首先从宏任务队列中选择最早的一个任务执行。常见的宏任务包括 setTimeout 、setInterval、I/O 操作(如网络请求)等。

在执行宏任务期间,如果有渲染任务需要执行,它们会被添加到渲染队列中。渲染队列中的任务包括更新 DOM 、执行 CSS 样式计算、布局(layout)和绘制(paint)等操作。

3.js对于渲染的阻塞

js的执行有可能会阻塞页面渲染吗?(也就是说任务太多,需要执行的时间太长)

结论:会阻塞渲染帧!

1.大量微任务可能堵塞页面渲染(因为是本次全部执行完毕)

2.raf(requestAnimationFrame)内注册大量回调可能堵塞页面渲染(因为是在下一次渲染之前执行)

3.定时器回调注册的宏任务并不会堵塞页面渲染(因为是下一次事件循环执行回调),但是可能会推迟页面渲染时间。

表现:

  1. 长时间的JavaScript执行阻塞了渲染:如果JavaScript代码执行时间过长,可能会阻塞浏览器执行其他渲染相关的操作,如布局、重绘和绘制。这会导致页面出现卡顿、不流畅的感觉,影响用户体验。
  2. 动画或过渡效果不平滑:如果在一次渲染帧中的JavaScript代码尚未执行完毕,而涉及到动画或过渡效果的样式又发生了变化,这可能导致动画效果不平滑,出现不连续或闪烁的现象。

为了避免长时间的JavaScript执行阻塞页面渲染,以提高用户体验,以下策略可以考虑:

  1. 优化 JavaScript 代码:通过减少不必要的计算、避免频繁的 DOM 操作、使用事件委托等技术来优化 JavaScript 代码,使其执行效率更高。
  2. 将耗时的任务分割为多个步骤:如果有一个长时间运行的任务,你可以将它分割成多个较小的步骤,并使用定时器(setTimeoutrequestAnimationFrame)来控制执行的节奏。这样可以使得每个步骤都在一个渲染帧内完成,从而避免长时间的阻塞。
  3. 使用 Web Workers:对于一些耗时的计算任务,可以考虑使用Web Workers来将其从主线程中分离出来,以充分利用多线程的并行处理能力,避免阻塞主线程的渲染
  4. 使用 requestIdleCallback:requestIdleCallback是一个浏览器提供的API,它可以让你在浏览器空闲时间执行任务,而不阻塞主线程的渲染过程。你可以将耗时较长的任务放在requestIdleCallback的回调函数中执行,以确保不会影响页面的渲染性能。

通过合理优化代码和使用适当的技术,可以确保JavaScript代码在渲染帧内能够及时执行完毕,以提高页面的渲染性能和用户体验。

requestAnimationFrame和requestIdleCallback是干嘛的?

在每一帧渲染完成后,在下一帧重绘之前如果有requestAnimationFrame,则调用之

如果在一帧渲染完后有空闲,就会执行requestIdleCallback注册的回调

什么是空闲时间呢?以60fps为例,如果浏览器渲染一帧发现不到16ms,那么剩余时间就算是空闲时间。

js事件循环和浏览器渲染帧的关系?

一帧里包含了多次事件循环,两次可以 render update 的 loop 之间为一次有内容变化的帧。

4.浏览器底层

浏览器也是单线程的吗?

大多数常见的浏览器(如 Chrome、Firefox、Safari)是基于单线程的模型。这意味着这些浏览器在主线程上执行 JavaScript 代码、处理用户交互事件、执行布局和渲染操作等都是按照顺序逐个执行的

单线程模型的主要原因是为了保证页面的安全性和简化编程模型。单线程模型可以避免多线程操作中的竞态条件、死锁和数据同步等问题,从而减少编程的复杂性。

然而,虽然主线程是单线程的,但现代浏览器引入了一些机制来充分利用计算机的多核处理能力和提高页面的响应性,例如:

  1. Web Workers:Web Workers 允许在后台启动额外的线程来执行 JavaScript 代码,使得一些耗时的计算任务可以在独立的线程中并行执行,而不会阻塞主线程的执行。但请注意,Web Workers 之间以及与主线程之间的通信需要使用消息传递的方式

  2. 异步操作:浏览器提供了一系列的异步 API,如定时器(setTimeoutsetInterval)、Promise、XHR(XMLHttpRequest)、fetch 等,这些 API 可以在主线程上启动异步任务,在任务完成后通过回调或 Promise 的方式进行处理。

  3. 事件循环(Event Loop):浏览器采用事件循环机制来处理异步任务的执行顺序。事件循环包含一个主线程和一个任务队列(Task Queue),异步任务在完成后会被放入任务队列中,然后在主线程空闲时按照顺序执行任务队列中的任务。

    注意:事件循环(Event Loop)是 JavaScript 运行时环境的一部分,而不是特定于浏览器的。它是 JavaScript 引擎中的一个机制,用于处理异步任务的执行顺序和协调事件的触发。

    JavaScript 引擎的设计和实现可以有很多不同的变种。一些常见的 JavaScript 引擎包括:

    • V8:由 Google 开发,用于 Google Chrome 浏览器和 Node.js 等环境。
    • SpiderMonkey:由 Mozilla 开发,用于 Firefox 浏览器。
    • JavaScriptCore:由 Apple 开发,用于 Safari 浏览器。
    • Chakra:由 Microsoft 开发,用于旧版的 Microsoft Edge 浏览器。
    • Nashorn:由 Oracle 开发,用于 Java 中的 JavaScript 执行环境。

    不同的 JavaScript 引擎可能在性能、特性和支持的标准方面有所不同,但它们都提供了执行 JavaScript 代码所需的核心功能。

这些机制使得在浏览器中可以实现并行处理和异步执行,从而提高了页面的响应性和用户体验。尽管浏览器主线程是单线程的,但通过合理利用这些机制,可以实现多线程和并发的效果。

浏览器的构成?

浏览器是一种用于访问和浏览互联网上网页的软件应用程序,通常由多个组件构成。下面是一个常见的浏览器的组成部分:

  1. 用户界面(User Interface):浏览器的用户界面包括地址栏、导航按钮、书签管理器等。它提供了用户与浏览器交互和控制的功能。
  2. 渲染引擎(Rendering Engine):渲染引擎负责解析和渲染网页内容,将 HTML、CSS 和 JavaScript 转换为可视化的网页。
  3. JavaScript 引擎(JavaScript Engine):JavaScript 引擎解释和执行 JavaScript 代码。它负责处理 JavaScript 相关的操作和功能,如变量赋值、函数调用、事件处理等。
  4. 布局引擎(Layout Engine):布局引擎负责计算和处理网页的布局和排版。它负责确定网页中每个元素的位置和大小,以便进行正确的渲染。
  5. 网络模块(Network Module):网络模块处理网络请求和数据传输,包括下载网页内容、加载资源文件(如图片、CSS、JavaScript 文件等)等。
  6. 数据存储(Data Storage):浏览器提供了多种数据存储机制,如浏览器缓存、Cookie、Web Storage(localStorage 和 sessionStorage)等,用于存储网页和用户数据。
  7. 安全模块(Security Module):安全模块负责保护用户的隐私和安全。它处理证书验证、安全连接(HTTPS)、弹出窗口阻止、恶意软件防护等安全相关的功能。
  8. 插件(Plug-ins):浏览器可以通过插件来扩展功能和支持特定的媒体格式或应用程序。常见的插件包括 Adobe Flash Player、PDF 阅读器等。

这些组件共同协作,使浏览器能够加载和渲染网页,执行 JavaScript 代码,与服务器进行通信,并提供丰富的功能和用户体验。不同浏览器的实现细节和组件可能有所不同,但基本的功能和模块通常是类似的。