css 模糊匹配-关于后端性能优化你必须了解的知识

当我们的资源内容无法复用时,直接为Cache-Control设置no-store,拒绝一切形式的缓存; 否则,考虑是否需要每次都向服务器确认缓存的有效性,如果需要,则将 Cache-Control 的值设置为 no-cache; 否则,考虑该资源是否可以被代理服务器缓存,并根据结果决定设置为私有还是公共; 然后考虑资源的过期时间,设置相应的max-age和s-maxage值; 最后,配置缓存所需的Etag、Last-Modified等Negotiate参数。

推送缓存

Push Cache是​​指HTTP2的服务器推送阶段存在的缓存。

了解有关 CDN 的更多信息

CDN有两个核心点,一是缓存,二是回源。

“缓存”是指将资源复制到CDN服务器的过程。 “回源”是指CDN发现自己没有这个资源(通常是缓存的数据已经过期),转而向根服务器(或其下层服务器)请求这个资源。

CDN常用于存储静态资源。 所谓“静态资源”就是JS、CSS、图片等不需要业务服务器估计的资源。 “动态资源”,顾名思义,就是需要前端实时动态生成的资源。 更常见的是依赖服务器端渲染的 JSP、ASP 或 HTML 页面。

那么“非纯静态资源”呢? 它是指需要服务器在页面之外进行额外计算的 HTML 页面。 具体来说,在我打开某个网站之前,该网站需要通过权限认证等一系列手段确认我的身份,然后决定是否将HTML页面呈现给我。 在这种情况下,HTML确实是静态的,但是它与业务服务器的运行耦合在一起。 我们把它扔到CDN上其实是不合适的。

另外,CDN的域名必须与主营业务服务器的域名不同。 否则,同一个域名下的cookie到处运行,浪费性能和流量费用。 将CDN域名放在不同的域名下可以完美避免不必要的cookie的出现!

图像优化

二进制数与色调的关系

在计算机中,像素由二进制数表示。 不同图像格式中像素与二进制数字的对应关系是不同的。 一个像素对应的二进制位数越多,可以表示的颜色种类越多,成像效果越柔和,相应的文件大小也越大。

一个二进制位代表两种颜色(0|1 对应黑|白)。 如果一种图片格式对应有n个二进制位,那么它可以呈现2^n种颜色。

计算图像大小

对于 100100 像素的图像,图像上有 10,000 个像素。 如果每个像素的值以RGBA存储,那么每个像素有4个通道,每个通道有1个字。 节(8 位 = 1 字节),因此图像大小约为 39KB (100001 * 4 / 1024)。

但在实际项目中,一张图片可能不需要用那么多颜色来显示,我们可以通过减少每个像素的调色板来减小图片的尺寸。

现在您知道了如何估计图像大小,那么您对于如何优化图像一定有两个想法:

图片类型要点

JPEG/JPG特点:有损压缩、体积小、加载快、不支持透明度,JPG最大的特点就是有损压缩。 这种高效的压缩算法使其成为一种非常轻量级的图像格式。 另一方面,尽管被称为“有损”压缩,JPG的压缩方式一直是一种高质量的压缩方式:当我们将图像体积压缩到原始体积的50%以下时,JPG仍然可以保持60% 。 % 质量。 但当它处理矢量图形、Logo等线条粗、色彩对比强烈的图像时,人工压缩造成的图像模糊就会相当明显。

PNG特点:无损压缩、高品质、大尺寸、支持透明。 PNG(便携式网络图形格式)是一种无损压缩的高保真图像格式。 8和24,这里是二进制数的位数。 根据我们后知识中提到的对应关系,8位PNG最多支持256种颜色,而24位PNG可以显示大约1600万种颜色。 PNG图像比JPG有更强的色调表现力,腰线的处理更饱满,对透明度有很好的支持。 它填补了我们上面谈到的JPG的局限性,唯一的缺陷是体积太大。

SVG特点:文本文件,体积小,不变形,兼容性好,SVG(Scalable Vector Graphics)是一种基于XML语法的图像格式。 它与本文提到的其他类型的图像有着本质的区别:SVG对图像的处理不是基于像素,而是基于对图像形状的描述。

Base64特点:文本文件、依赖编码、小图标解决方案,Base64不是一种图像格式,而是一种编码方法。 Base64 与 Sprite 一样,作为小图标解决方案存在。

WebP的特点:年轻的多面手,WebP可以像JPEG一样处理细节丰富的图片,可以像PNG一样支持透明度css 模糊匹配,还可以像GIF一样显示动态图片——它结合了多种图片文件格式的优点。 但虽然年轻css 模糊匹配,但兼容性上却存在一些问题。

渲染优化

客户端渲染

客户端渲染模式下,服务器会将渲染所需的静态文件发送给客户端。 客户端加载后,会在浏览器中运行JS,并根据JS执行结果生成相应的DOM。 页面上渲染的内容,你在html源文件中是找不到的——这就是它的特点。

服务器端渲染

在服务器端渲染模式下,当用户第一次请求页面时,服务器将所需的组件或页面渲染成HTML字符串,然后返回给客户端。 页面上呈现的内容也可以在html源文件中找到。 服务端渲染解决了一个非常关键的性能问题——首屏加载速度太慢,同时也解决了SEO搜索引擎的问题。

浏览器渲染流程分析

浏览器的渲染机制通常分为以下几个步骤:

当渲染 DOM 时,浏览器实际上所做的是:

基于渲染过程的CSS优化建议

CSS选择器是从右到左匹配的,比如#myList li {} 实际成本相当高。

CSS 屏蔽

CSS 是一种阻塞资源。 在构建CSSOM的过程中,浏览器不会渲染任何处理过的内容。 即使DOM已经解析完毕,只要CSSOM不行,那么渲染也不行。 我们将CSS放在head标签中,并尽早启用CDN,以优化静态资源的加载率。

JS 阻塞

JS引擎独立于渲染引擎而存在。 无论我们的 JS 代码插入到文档中的何处,它都会在那里执行。 当 HTML 解析器遇到 script 标签时,它会暂停渲染过程并将控制权传递给 JS 引擎。 JS引擎会直接执行内联JS代码,外部JS文件必须先获取脚本然后执行。 JS引擎运行完毕后,浏览器会将控制权返回给渲染引擎,继续建立CSSOM和DOM。

DOM渲染优化

首先了解回流焊和重绘

重绘不一定会引起回流,回流一定会引起重绘。 回流不仅仅是重新绘制,而且会带来更多的开销。 开发时,从代码层面入手,尽可能减少回流、重绘的次数。

实例分析

<span class="hljs-meta" style="letter-spacing: 1px;font-size: inherit;line-height: inherit;color: rgb(153, 153, 153);font-weight: bold;overflow-wrap: inherit !important;word-break: inherit !important;">
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DOM操作测试</title>
</head>
<body>
  <div id="container"></div>
</body>
</html>

for(var count=0;count<10000;count++){ 
  document.getElementById('container').innerHTML+='我是一个小测试'  //我们每一次循环都调用 DOM 接口重新获取了一次 container 元素,额外开销

进化一:

// 只获取一次container
let container = document.getElementById('container')
for(let count=0;count<10000;count++){ 
  container.innerHTML += '我是一个小测试'

进化二:

//减少不必要的DOM更改
let container = document.getElementById('container')
let content = ''
for(let count=0;count<10000;count++){ 
  // 先对内容进行操作
  content += '我是一个小测试'

// 内容处理好了,最后再触发DOM的更改
container.innerHTML = content

事实上,考虑到JS的运行速度,它比DOM要快得多。 我们减少DOM操作的核心思想是让JS分担DOM的压力。

在 DOM Fragment 中,DocumentFragment 接口表示没有父文档的最小文档对象。 它用作 Document 的轻量级版本,用于存储格式化或未格式化的 XML 片段。 由于DocumentFragment并不是真实DOM树的一部分,因此它的改变不会导致DOM树的重新渲染(回流),也不会造成性能问题。

进化三:

let container = document.getElementById('container')
// 创建一个DOM Fragment对象作为容器
let content = document.createDocumentFragment()
for(let count=0;count<10000;count++){
  // span此时可以通过DOM API去创建
  let oSpan = document.createElement("span")
  oSpan.innerHTML = '我是一个小测试'
  // 像操作真实DOM一样操作DOM Fragment对象
  content.appendChild(oSpan)
}
// 内容处理好了,最后再触发真实DOM的更改
container.appendChild(content)

进化四:

如何解决渲染超过10000个调优数据时不卡屏的问题?

如何在不阻塞页面的情况下渲染数据,也就是说,你不能一次渲染几万个item,而是应该一次渲染部分DOM,然后你可以通过requestAnimationFrame每16ms刷新一次。


<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <ul>
      控件
    </ul>
    <script>
      setTimeout(() => {
        // 插入十万条数据
        const total = 100000
        // 一次插入 20 条,如果觉得性能不好就减少
        const once = 20
        // 渲染数据总共需要几次
        const loopCount = total / once
        let countOfRender = 0
        let ul = document.querySelector('ul')
        function add({
          // 优化性能,插入不会造成回流
          const fragment = document.createDocumentFragment()
          for (let i = 0; i < once; i++) {
            const li = document.createElement('li')
            li.innerText = Math.floor(Math.random() * total)
            fragment.appendChild(li)
          }
          ul.appendChild(fragment)
          countOfRender += 1
          loop()
        }
        function loop({
          if (countOfRender < loopCount) {
            window.requestAnimationFrame(add)
          }
        }
        loop()
      }, 0)
    
</script>
  </body>
</html>

window.requestAnimationFrame() 方法告诉浏览器您希望执行动画,并请求浏览器在下次重绘之前调用指定的函数来更新动画。 该方法接受一个回调函数作为参数,并且该回调函数将在浏览器重绘之前被调用。

注意:如果你想在最后一次重绘时形成另一个动画帧,你的弹跳库必须调用

请求动画帧()。

事件循环

我们先了解一下javascript的运行机制,这对渲染很有帮助。

事件循环中有两种类型的异步队列:宏(宏任务)队列和微(微任务)队列。

常见的宏任务如:setTimeout、setInterval、setImmediate、脚本(整体代码)、I/O操作、UI渲染等。

常见的微任务如:process.nextTick、Promise、MutationObserver等。

实例分析:

// task是一个用于修改DOM的回调
setTimeout(task, 0)

通过上面的代码,任务现在被放入宏队列中。 不过,由于script脚本本身是一个宏任务,所以这次执行完script脚本后,下一步就是处理微队列,然后再执行一次render,必须等待下一次循环。

Promise.resolve().then(task)

上面的代码中,我们已经执行完了script脚本,是不是应该立即处理微任务队列呢? 微任务处理完毕、DOM修改后,可以立即启动渲染流程——无需消耗额外的渲染,无需等待一个风暴周期,直接呈现最即时的更新结果用户。

上面提到了重绘和回流,Event Loop,但是很多人不知道的是,重绘和回流都和Event Loop有关。

节流防抖

当用户滚动时,就会触发滚动风暴,用户的每一次滚动都会触发我们的窃听功能。 函数执行是性能密集型的,频繁响应风暴会导致大量不必要的页面估计。 因此,我们需要对这些可能频繁触发的风暴进行进一步优化。 节流和聚焦是必要的!