页面结构优化/性能优化

1,851 阅读10分钟

前言

对于一个前端开发工程师来说,提高网站性能方面是非常重要的,这可以更好的提高用户体验。

浏览器简介

浏览器的主要功能总结起来就是一句话:将用户输入的URL转为可视化的图像。

浏览器的内核(渲染引擎)

渲染引擎,负责对网页语法的解释(如HTML、JavaScript)并渲染网页。所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。

进程与线程

进程: 程序的一次执行,他占有一片独有的内存空间,是操作系统的基本单位。

  1. 一个进程中至少有一个运行的线程:主线程。进程启动后自动创建。
  2. 一个进程中也可以同时运行多个线程,程序是多线程运行的。
  3. 一个进程内的数据可以提供其中的多个线程共享,多个进程之间的数据是不能直接共享的。

线程: 是建成内一个独立执行的单位,是CPU调度的最小单元,程序运行的基本单位。

线程池:保存多个线程对象的容器,实现线程对象的反复利用。

现代浏览器:多进程、多线程模型

  1. 不堪回首的过去: 当你通过浏览器打开多个页面的时候,如果其中一个页面不响应了或者崩溃了,那么随之而来的将会是更不幸的事情,你打开的所有页面都而不到响应,最让人不能忍受的是,其中一些页面还包含了一些未发送、未保存的信息
  2. 浏览器厂商如何解决: 采用多进程模型,该模型可以带来的好处是:当第三方插件崩溃时,也不会影响到整个浏览器的稳定性,安全。
  3. 浏览器有哪些进程
    1. Browser进程: 浏览器的主进程,负责浏览器页面的显示,和各个页面的管理。浏览器中所有其他类型进程的祖先,负责其他进程的创建和销毁,有且仅只有一个。
    2. Render进程: 网页渲染进程,负责页面的渲染,可以有多个,渲染进程的数量不一定等于你打开的网页的个数
    3. 各种插件进程
    4. GPU:移动设备的浏览器可能不太一样。安卓不支持插件所以没有插件进程,GPU演化成Broser进程的一个县城

多线程的目的主要是保持用户界面的高度响应。 例如:为了不让Browser进程进程的UI线程被其他耗时的操作(本地文件的读写)所阻塞,那么我们就把这些操作放到分线程中去处理。在render进程中,为了不让其他操作阻止渲染线程的高速执行,我们通常会将渲染过程管道化,利用计算机的多核优势,让渲染的不同阶段在不同线程中执行。

浏览器渲染原理

主要模块

一个渲染引擎主要包括:HTML解析器、CSS解析器、JavaScript引擎、布局layout模块、绘图模块

  • HTML解析器:解析HTML文档的解析器,主要作用是将HTML解释为DOM树,
  • CSS解析器:他的作用是为DOM的各个元素计算出样式信息,为布局提供基础设施。
  • JavaScript引擎:使用JavaScript代码可以修改网页的内容,也能修改css信息,JavaScript引擎能够解释JavaScript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息
  • 布局layout:在DOM创建后webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,新城一个表达这所有信息的内部表达模型
  • 绘图模块paint:使用图形库将布局计算后的各个网页得节点会址常图像结果

渲染过程

浏览器渲染页面的整个过程:浏览器会从上到下解析文档。

  1. 遇见HTML标记,调用HTML解析器解析成对应的token(一个token就是一个标签文本的序列化)并构建DOM树(就是一块内存,保存着token)。
  2. 遇见style/link 标记调用CSS解析器处理CSS标记并构建CSS样式树。
  3. 遇见script调用JavaScript解析器处理script镖旗,绑定事件、修改DOM树/CSS树 等。
  4. 将DOM树和CSS树合并成一个渲染树。
  5. 根据渲染树来渲染,以计算每个节点上的几何信息(这一过程需要依赖图形库)。
  6. 将各个节点回执到屏幕上。

style标签样式渲染

  1. style 标签中的样式由html解析器进行解析

  2. 浏览器加载资源是异步的

  3. 页面style标签写的样式是异步解析的

  4. link进来的样式,是由css解析器去解析,而且是同步解析的

  5. css解析器会阻塞页面的渲染(也可以说link 进来的外部样式会阻塞页面的渲染,利用它避免闪屏)

  6. 推荐使用link方式引入样式

验证css阻塞和js阻塞

这是文件目录

这是用node写的文件

const http = require("http");
const fs = require("fs");
http.createServer((req,res)=>{ 
    if(req.url === '/index.html'){
        fs.readFile("./staitc/html/index.html",(err,data)=>{
            if(err){
                res.end("读取文件有误")
            }else{
                res.end(data)
            }
        })
    }
    if(req.url === '/css/common.css'){
        fs.readFile("./staitc/css/common.css",(err,data)=>{
            if(err){
                res.end("读取文件有误")
            }else{
                res.end(data)
            }
        })
    }
}).listen(3000)

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 400px;
            height: 400px;
            background: red;
        }
    </style>
    <link rel="stylesheet" href="/css/common.css">
</head>

<body>
    <div></div>
</body>

</html>

common.css

div {
    width: 200px;
    height: 200px;
    background: blue;
}

当我们强制刷新N次发现每次都来的都是common.css的样式,这里就不多描述了,大家都懂

验证css阻塞

上面我们说到link进来的样式会阻止页面的渲染。那么我们现在怎么没有看到呢。

原因:1. 我们link进来的样式很简单 2. 这个样式文件在我们自己的本地服务器上,拿取的很快。

为了更直观的验证css阻塞页面的渲染。我们把common.css文件延迟3000毫秒后返回。

修改index.js文件

 if (req.url === '/css/common.css') {
        fs.readFile("./staitc/css/common.css", (err, data) => {
            if (err) {
                res.end("读取文件有误")
            } else {
                setTimeout(() => {
                    res.end(data)
                }, 3000)
            }
        })
    }

保存后,通过 node index.js 重启服务器,然后在页面中打开http://localhost:3000/index.html,

这时候我们发现,浏览器一直在转圈圈,页面一直没有更新,直到3秒后页面才渲染。

如果css不阻塞页面的渲染,那么首次选渲染会执行index.html中style中的样式,然后等common.css返回再渲染common.css中的样式。

由此证明link进来的css会阻塞页面的渲染

link进来的css不会阻塞页面解析

下面我们修改一下index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 400px;
            height: 400px;
            background: red;
        }
    </style>
   
    <script>
        setTimeout(() => {
            const div = document.querySelector("div")
            console.log(div)
        })
    </script>
     <link rel="stylesheet" href="/css/common.css">
</head>

<body>
    <div></div> 
</body> 
</html>

然后再次访问http://localhost:3000/index.html,我们惊奇的发现script语句能正常执行并输出

可以得知,此时的DOM树至少已经解析到div那里,而此时的css还没有加载完成,这说明css不会阻塞页面的解析

link进来的css会不会阻塞后面js的执行?

现在我们再次修改index.html的结构

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 400px;
            height: 400px;
            background: red;
        }
    </style>

    <script>
        console.log('before css')
        var startDate = new Date()
    </script>
    <link rel="stylesheet" href="/css/common.css">
</head>

<body>
    <div></div>
    <script>
        var endDate = new Date()
        console.log('after css')
        console.log('经过了' + (endDate - startDate) + 'ms')
    </script>
</body>

</html>

然后再次访问http://localhost:3000/index.html,我们惊奇的发现script语句能正常执行并输出

由上图我们可以看出,位于css加载语句前的那个js代码先执行了,但是位于css加载语句后面的代码迟迟没有执行,直到css加载完成后,它才执行。这也就说明了,css加载会阻塞后面的js语句的执行

由此可以证明 link进来的CSS会阻塞后面js语句的执行

验证js阻塞

js阻塞页面解析

js阻塞页面渲染

css 图层

浏览器在渲染页面时,会将页面分层多个图层,图层有大有小,每个图层上都由一个或多个节点。在渲染DOM的时候,浏览器所作的工作实际是:

  1. 获取DOM分割成多个图层
  2. 对每个图层的节点计算样式结果 ---- (Recalculate style 样式重计算)
  3. 为每个节点生成图形和位置 ---- (Layout 重排,回流)
  4. 将每个节点绘制填充到图层位图中--- (Paint 重绘)
  5. 图层作为纹理上传到GPU
  6. 组合多个图层到页面上生成最终屏幕图像。---(Composite Layers 图层重组)

图层创建条件

Chrome浏览器满足一下任意情况就会创建图层

  1. 拥有3D变换的属性
  2. 使用加速视频解码的video 节点
  3. canvas 节点
  4. css 动画节点
  5. 拥有css加速属性的元素

重绘

重绘是一个元素外观的改变所触发浏览器的行为,例如改变outline、 背景色的属性。浏览器会根据元素的新属性重新使元素呈现新的外观,重回不会重新布局,所以并不一定伴随重排。

需要注意的是,重绘是以图层为单位,如果图层中某个元素需要重绘没那么整个图层都需要重绘。

比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重绘整个图层的其他节点,然后生成最终的屠城,所以这需要通过特殊的方式来强制GIF图属于一个自己的图层(tanslateZ(0) 或者 tanslate3D(0,0,0))css动画也一样(好在绝大部分情况下浏览器会为css3动画节点创建图层)

重排(回流)

渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值得过程称之为布局或者重排

重绘不一定重排,比如改变某个网页元素的颜色,就只会触发重绘,不会触发重排,因为布局没发生改变。但是重排一定产生重绘。比如改变某个元素的位置,就会同时触发重排和重绘,因为布局改变了。

优化重绘回流

  1. 元素位置的移动变换时,尽量使用tansform来代替对top、left的操作
  2. 批量修改样式时:使用 el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;'
  3. 利用文档碎片
  4. 将DOM离线后再修改:由于Display属性为node的元素不在渲染树中,对于隐藏的元素操作不会引发其他元素的重排。
  5. 为动画元素新建图层,提高动画元素的z-index
  6. 避免触发同步布局事件
  7. css硬件加速