前端性能——代码逻辑优化


关键渲染路径

当页面输入URL,到页面加载并渲染,需要经过几个关键步骤

  • DNS解析:将域名解析为ip地址
  • TCP连接: 三次握手
  • 服务端接收请求,返回数据,客户端接收请求,解析数据开启关键渲染路径

1 解析html,生成DOM Tree

对HTML文档进行解析,并在解析 HTML 的过程中发出了页面渲染所需的各种外部资源请求

<!DOCTYPE html>
<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>标题</title>
  <style>
    body {
      font-size: 20px;
    }
    div {
      width: 100px;
      height: 100px;
    }
    span {
      color: #000;
    }
    p {
      display: none
    }
  </style>
</head>
<body>
  <div class="box1">
    <span>span-box1</span>
  </div>
  <div class="box2">
    <span>span-box2</span>
  </div>
  <p>---p---</p>
</body>
</html>

在这里插入图片描述

2 CSS 解析,生成CSSOM 树

在这里插入图片描述

3 DOM与CSSOM 生成 Render Tree

  • step1: 从 DOM 树的根节点开始遍历,筛选出所有可见的节点;
  • step2:仅针对可见节点,为其匹配 CSSOM 中的 CSS 规则;
  • step3:发射可见节点(连同其内容和计算的样式)。
    遇到<script>会暂停渲染,有限执行js,执行结束后继续生成Render Tree

在这里插入图片描述

4 布局、绘制

浏览器对渲染树进行遍历,将元素间嵌套关系以盒子模型的形式写入文档流。

盒模型在布局过程中会计算出元素确切的大小和定位。计算完毕后,相应的信息被写回渲染树上,就形成了“布局渲染树”。同时,每一个元素盒子也都携带着自身的样式信息,作为后续绘制的依据。

渲染优化——减少重排/重绘

浏览器渲染优化可以从很多角度考虑,核心思路只有一个:减少页面重排/重绘的次数或者数量,浏览器随时都会发生更新,以下基本操作会导致页面重新渲染。重排必定重绘,重绘不一定必须要重排
重排(Reflow)

  • 改变元素的位置或尺寸。
  • 添加或删除可见的 DOM 元素。
  • 改变元素的字体大小、行高、内容或其他影响布局的属性。
  • 计算或获取元素的 offsetWidth、offsetHeight、scrollTop 或 scrollLeft 等属性。
  • 使用 float、position 或 display 等属性改变元素的显示方式。
  • 当父元素的宽高发生变化时,子元素也会触发重排。
  • 使用表格布局时,某些操作可能会导致整个表格的重排。

重绘(Repaint)

  • 改变元素的颜色、背景、边框或阴影等非几何属性。
  • 给元素添加或删除一个类名,导致其样式发生变化。
  • 使用 CSS 动画或过渡时,浏览器需要重新绘制元素。
  • 当元素的内容发生变化时,浏览器需要重新绘制元素。

1 CSS优化

1-1 减少JS动态修改样式

transform可以借用GPU对动画进行处理,省去布局和重绘。

使用javascript动态修改样式会引起频繁的重排/重绘,需要谨慎使用:

let Div = document.getElementsById('div');
Div.style.width = '100px'; // ⚠️ 

同时尽量避免在循环中修改元素的样式:

let imgDiv = document.getElementsByClassName('img')
for(let i = 0; i<imgDiv.length; i++) {
				imgDiv[i].style.width = 100 + 'px'
				imgDiv[i].style.height = 100 + 'px'
			}  // ⚠️ 

1-2 使用 transform 和 opacity

transform(动画)可以opacity(透明)浏览器会借助GPU进行处理,省去布局和重绘。

使用javascript动态修改样式会引起频繁的重排/重绘,需要谨慎使用:

let Div = document.getElementsById('div');
Div.style.width = '100px'; // ⚠️ 

1-3 contain、will-change

  • contain

contain 属性支持许多值,如 none、strict、content 、layout以及这些值的组合(例如 contain: layout paint),每个值都提供了不同程度的隔离。选择哪个值取决于你的具体需求和期望的隔离级别。

使用javascript动态修改样式会引起频繁的重排/重绘,需要谨慎使用:

.example {
    contain: layout paint;
}
  • 当为类名为example的元素设置如上属性时,该元素及其子元素将不会影响外部的布局与绘制,同时也不会被外部的布局绘制所影响。在某些情况下,特别是对于大型或复杂的页面布局,这种隔离可以帮助减少重排(reflow)和重绘(repaint)的次数。在使用 contain属性时,应该仔细考虑其对页面布局和性能的具体影响,过度使用或不当使用可能会导致性能问题
  • will-change

当你知道一个元素即将被动画或过渡效果修改时,可以使用will-change属性提前告知浏览器。这样,浏览器可以针对这些属性进行优化,从而提高动画的流畅性和性能。

对于一个即将进行缩放和透明度变化的元素,可以这样设置:

.element {
    will-change: transform, opacity;
    transition: transform 0.5s, opacity 0.5s;
}
.element:hover {
    transform: scale(1.5);
    opacity: 0.5;
}

will-change属性应该只在确实需要优化的场景中使用。对于简单的动画或变化,使用will-change可能并不会带来明显的性能提升。大量使用可能会造成反效果

1-4 减少table布局的使用

2 优化DOM操作

2-1 使用 documentFragment / display: none 来添加 / 删除多个元素

  • 添加多个元素
Ïconst list = document.getElementById('list')

// ===========创建一个文档片段,此时还没有插入到 DOM 结构中⭐==========
const frag = document.createDocumentFragment()

for (let i  = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
  • 删除多个元素

将元素的 display 属性设置为 none 会使元素从文档流中完全移除,这意味着它不仅在视觉上不可见,而且不再占用任何空间。

  • 由于 display: none 隐藏的元素不再参与布局,因此任何与该元素相关的布局变化都不会触发重排。此外,如果该元素之前已经在页面上绘制过,隐藏它也不会触发重绘(因为它已经从 DOM 树中移除,不再需要渲染)。
  • 重新显示元素:当将 display 属性从 none 改回其他值(如 block)时,元素会重新进入文档流,这通常会触发一次重排和重绘,因为浏览器需要重新计算布局并绘制该元素。
  • 尽量将布局变动限制在较小的 DOM 子树内,避免全局性的布局变化。因为元素隐藏后有可能导致其他元素的重排重绘

2-2 为查询做缓存

const pList = document.getElementsByTagName('p')
const length = pList.length // ✅ 缓存length,只进行一次查询
for (let i=0;i<length;i++){
    // xxx
}

const pList = document.getElementsByTagName('p')
for (let i=0;i<pList.length;i++){   // ⚠️ 没有缓存length,查询多次
    // xxx
}

2-3 事件代理减少开销

事件代理

3 HTML加载优化

3-1 减少iframe使用

  • iframe的加载会阻塞父文档的加载 只有当iframe加载完成时,才会触发父文档的onload事件
  • 相较于原文档,iframe上元素的创建消耗会更大

当使用iframe时,可以先加载空白iframe,随后设置内容,减少父文档load时间

 <iframe id='iframe'></iframe>
 
  document.addEventListener('DOMContentLoaded', function() {
    // DOM 完全加载和解析完成后的代码
    document.getElementById('iframe'),setAttribute('src', 'xxx.example.com')
})

3-2 js位置

  • js的执行会阻塞页面的渲染,因此将js放在body的最下面
  • 使用js时,尽量使用外链,会有助于提高加载速度
 <script src="defer.js" defer></script>
 // defer: 告诉浏览器立即下载脚本,但延迟执行直到文档完全解析和显示。适用于不依赖于文档中其他脚本的脚本。
 <script src="async.js" async></script>
// async: 告诉浏览器异步下载脚本,下载完成后立即执行,不保证执行顺序。适用于可以独立运行的脚本。

3-2 减少html元素的嵌套层级

4 节流、防抖、rAF

4-1 节流

原理:

  • 节流的核心原理是在规定的时间间隔内只允许某个函数执行一次。
  • 当事件首次触发时,会立即执行处理函数,然后在指定的时间间隔内忽略后续的事件触发,直到时间间隔结束后再允许执行下一次事件处理。

使用场景:

  • 页面滚动:限制滚动事件的触发频率,避免频繁触发导致的性能问题。
  • 窗口大小调整:减少resize事件的触发次数,避免频繁计算。
  • 按钮点击:防止按钮在短时间内被多次点击,造成重复提交或多次触发。
onst div1 = document.getElementById('div1')
//==========节流===========
function throttle(fn, delay = 100) {
    let timer = null

    return function () {
        if (timer) { // 连续触发时,有仍在执行的timer则停止
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

div1.addEventListener('drag', throttle(function (e) {
    console.log(e.offsetX, e.offsetY)
}))

4-2 防抖

原理:

  • 防抖的核心原理是在事件被触发后等待一段时间,如果在这段时间内再次触发了相同的事件,则重新计时。
  • 只有当事件触发后的一段时间内没有再次触发时,才执行相应的操作。

使用场景:

  • 搜索框输入:在用户停止输入一段时间后再发送搜索请求,避免频繁的请求。
  • 表单验证:在用户停止输入后再进行表单验证,减少不必要的验证请求。
const input1 = document.getElementById('input1')
// ========防抖===========
function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null

    return function () {
        if (timer) {             //连续输入的时候,中止上一个输入的timer
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

input1.addEventListener('keyup', debounce(function () { 
    console.log(input1.value)
}, 600))

4-3 requestAnimationFrame

浏览器动画之requestAnimationFrame
使用requestAnimationFrame减少浏览器重绘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值