《六》移动端性能优化

性能就是页面的响应速度。优化性能就是提升页面的响应速度。

性能优化在PC 端和移动端都适用。
与 PC 端相比,移动端网络速度慢,设备性能低,同一份代码,可能在 PC 端没有性能问题,但是在移动端就会有性能问题。所以相比 PC 端,移动端更需要性能优化。

页面的响应速度可以分为两个方面:

  1. 打开页面到实际能够正常使用的时间,分为两个阶段:
    • 网络请求获取资源的时间。
    • 页面加载和渲染的时间。
  2. 与页面进行交互的流畅程度,分为一个阶段:
    • JS 脚本的执行速度。

各阶段的性能优化点:

网络请求过程中的优化点:

《六》移动端性能优化_第1张图片
《六》移动端性能优化_第2张图片
网络请求的过程:当在浏览器的地址栏中输入相应的网址的时候,浏览器会先查找一下自身的缓存,看是否有相关地址的缓存,如果有的话,直接使用缓存;如果没有缓存,就会解析相应的地址,先到浏览器自身的 IP 池里面查找一下是否访问过相关的 IP,如果有的话,直接拿到;如果没有的话,到 DNS 域名解析服务器获取对应的 IP;拿到 IP 地址之后,在浏览器和服务器之间建立 TCP 连接;然后浏览器向服务器发送请求,服务器向浏览器发送响应。

为什么会有排队等待的时间呢?浏览器和每个域名之间能够建立的 TCP 连接通道是有数量限制的(例如:Chrome 浏览器限制的数量是 6 个,也就是从每个域名下最多能并发地获取 6 个资源),如果超过数量限制,后面的资源就需要排队等待前面的资源获取完成之后,将 TCP 通道让出来,后面的资源才能获取。

  1. 针对排队等待的时间:将多个资源分布在不同域上,减少请求队列的等待时间。
    原因是:浏览器为每个域名分配的并发通道有限。例如:Chrome 浏览器为每个域名分配的并发通道是 6 个,也就是每个域名下同时只能加载 6 个资源,后面的资源都需要等待。如果不想让它们等待,就可以将资源分配在不同的域上。

    多个域意味着更多的 DNS 查询时间,通常把域名拆分到 3 ~ 5 个比较合适。

  2. 针对查询 IP 地址的时间:通过 dns-perfetch 减少 DNS 查询时间。
    原因是:当浏览器向第三方服务器请求资源时,必须先将该域名解析为 IP 地址,这个过程就是 DNS 解析,DNS 解析会导致请求增加明显的延迟。dns-perfetch 是一种DNS 预解析技术,在加载网页的过程中会对网页中的域名提前进行解析缓存,之后用到该域名的时候,可以直接从缓存中拿到对应的 IP 地址,减少请求中的延迟。

    dns-perfetch 仅对跨域的域上的 DSN 查询有效,不要使用它指向当前网页所在的域名。因为到浏览器解析到这一行代码时,当前网页所在的域名背后的 IP 已被解析。
    已经解析过的域名不要再添加 dns-perfetch ,多页面重复DNS预解析会增加重复 DNS 查询次数。

    
    
  3. 针对建立 TCP 连接的时间:减少 HTTP 请求数量。
    原因是:在请求的过程中,建立 TCP 连接非常耗时,可以通过减少 HTTP 请求,一定程度上来减少建立 TCP 连接的次数。

    并不是建立一次 TCP 连接只能完成一次请求和响应,但是具体能完成多少次,是无法控制的。

    • 合并资源:合并 CSS、JS 等文件。

      并不是说合并后的资源数量越少越好,需要考虑到合并后的资源体积不能过大,否则会导致下载时间过长。

    • 内联首屏的相关代码:将首屏相关的 CSS、JS 代码直接内联写到 HTML 文件中,这样就不需要发送额外的请求去获取这些代码,当 HTML 文件下载并解析完成后,直接就可以显示出首屏的内容了。

      更快地加载页面首屏内容,无需考虑整个页面。

    • 使用缓存:浏览器缓存、localStorage 等。
    • 图片优化:可以使用 CSS 画图来代替简单的图片;使用 CSS 精灵图来合并小图标;使用 Data URL 将小图标直接内嵌到 HTML 中。
  4. 针对下载资源的时间:减少请求资源的大小。

    资源越小,下载得越快;资源越大,下载得越慢。

    • 压缩资源:对 HTML、CSS 进行压缩,对 JS 进行压缩和混淆。

      压缩:去除代码中的不必要的空格、换行等,使源码都压缩为几行内容。
      混淆:将有语义的变量替换为单个的可读性差的字符等。代码混淆可以起到反爬的作用。

    • 开启 gzip 压缩。

      可以通过 response headers 中的 content-encoding 来查看是否开启了 gzip 压缩。

    • 减少 Cookie 的体积。
      原因是:在向当前域发送请求时,每次请求都会携带上当前域下的 Cookie。
    • 图片优化:使用图标字体来代替简单的图标;压缩图片;选择合适的图片大小(例如:利用媒体查询,不同大小的屏幕加载不同大小的图片);选择合适的图片类型。

页面加载和渲染过程中的优化点:

《六》移动端性能优化_第3张图片
页面加载和渲染的过程:浏览器解析 HTML,生成】成 DOM 树;同时解析 CSS,生成 CSS 规则树;CSS 规则附着到 DOM 树上,生成渲染树;浏览器对渲染树中各节点的布局进行计算,然后绘制出来,显示在显示器上。

  1. 针对解析 HTML 生成 DOM 树:
    • 减少 DOM 元素的数量和嵌套层级。
    • 避免使用 table 布局,使用其他标签代替。
      原因是:一般来说,浏览器解析标签都是解析一行就显示一行,但是 table 是作为一个整体解析的,要等整个表格都解析完才会显示;而且可能很小的一点改动,也会造成整个 table 的重排。
  2. 针对解析 CSS 生成 CSS 规则树:
    • 选择器优化:
      • 不要使用嵌套过多、过于复杂的选择器。
        原因是:浏览器在解析 CSS 选择器的时候,是从右往左解析的。例如:ul li a,浏览器会先找到所有的 a 标签,然后再去找到的 a 标签中找上级节点是 li 的 a 标签,然后再去找到的 a 标签中找上级节点是 ul 的 a 标签。嵌套越多,查找的次数就越多。
      • 避免过多的通配符选择器。
        原因是:浏览器看到通配符选择器,会选中所有的选择器。
      • 移除空样式的选择器。
        .list { }
        
    • 其他优化:
      • 提取共用的样式。
      • 避免使用 CSS 自身的 @import 导入 CSS。
        原因是:页面不会等待这种方式引入的样式表加载完毕,就会立即渲染 HTML 结构,会导致页面有几秒钟没有样式的情况。
      • CSS 一般在 head 中引入;JS 一般在 body 的末尾引入。
        CSS 一般在 head 中引入原因是:需要一渲染出页面的时候就已经携带上样式,放在 head 中引入能够避免刚开始的时候没有样式,后面才突然有了样式。
        JS 一般在 body 的末尾引入原因是:并不需要一渲染出页面功能立马就可以使用,放在 body 的末尾引入能够避免影响后面内容的渲染。
  3. 针对生成渲染树之后布局与绘制:减少重排与重绘。

    元素的尺寸、位置、隐藏等属性改变时,浏览器需要重新计算,称为重排(重布局、回流)。
    元素的外观、风格等属性改变时,浏览器只需要重新绘制,称为重绘。
    重排一定会引起重绘,重绘不一定会引起重排。重排比重绘更加影响性能。

  4. 图片优化:图片懒加载与预加载。
  5. 动画优化:优先使用 CSS3 过渡和动画;如果动画过于复杂,必须使用 JS 的话,优先使用 requestAnimationFrame,尽量不要使用 setTimeout 和 setInterval。

JS 脚本中的优化点:

  1. DOM 操作优化:
    • 获取 DOM 元素优化:
      • 获取单个元素:优先使用 id 选择器来获取单个元素。
        document.getElementById('#list') // 推荐
        
      • 获取多个元素:尽量直接通过元素本身的 className 来获取。
        document.getElementByClassName('.item') // 推荐
        
    • 减少 DOM 操作次数优化:

      操作 DOM 是很慢的。

      • 将获取到的 DOM 元素通过变量保存起来,这样之后再次使用的时候就不需要重新获取了。
        const listEl = document.getElementById('#list') // 推荐
        
      • 新创建的元素,完成必要操作后再添加到页面中。
        // 不推荐
        for (const item of todoData) {
        	const liEl = document.createElement('li')
        	listEl.appendChild('liEl') // 此时 liEl 元素已经显示在页面中,再对它进行操作会引起浏览器并的重排和重绘
        	liEl.className = 'item'
        	liEl.innerHTML = item 
        }
        
        // 推荐
        for (const item of todoData) {
        	const liEl = document.createElement('li')
        	liEl.className = 'item'
        	liEl.innerHTML = item // 此时 liEl 元素还没有显示在页面中,浏览器并不会对没有显示出来的元素进行重排和重绘
        	listEl.appendChild('liEl')
        }
        
      • 使用 DocumentFragment 优化多次的 appendChild()。
        // 不推荐。也是在循环中通过 listEl.appendChild() 多次操作了 listEl 元素
        for (const item of todoData) {
        	const liEl = document.createElement('li')
        	liEl.className = 'item'
        	liEl.innerHTML = item // 此时 liEl 元素还没有显示在页面中,浏览器并不会对没有显示出来的元素进行重排和重绘,因此这部分代码对性能影响不大
        	listEl.appendChild('liEl')
        }
        
        // 推荐。
        let liFragment = document.createDocumentFragment()
        for (const item of todoData) {
        	const liEl = document.createElement('li')
        	liEl.className = 'item'
        	liEl.innerHTML = item
        	liFragment.appendChild('liEl')
        }
        listEl.appendChild(liFragment)
        
      • 避免在循环中多次使用 innerHTML,在循环结束后使用一次即可。
        // 不推荐
        for (const item of todoData) {
        	listEl.innerHTML += `
      • ${item}
      • ` }
        // 推荐
        let html = ''
        for (const item of todoData) {
        	html += `
      • ${item}
      • ` } listEl.innerHTML = html
      • 不要直接通过 JS 修改元素的 style,可以通过添加/移除 class 来修改元素的样式。
        // 不推荐。每一行代码都会引发一次浏览器的重排或者重绘
        liEl.style.width = '100px' // 重排、重绘
        liEl.style.height = '100px' // 重排、重绘
        liEl.style.backgroundColor = 'blue' // 重绘
        
        // 推荐。只会引发一次浏览器的重排或者重绘
        .li {
        	width: 100px;
        	height: 100px;
        	background-color: blue;
        }
        liEl.classList.add('li')
        
      • 注意强制回流,可以将结果保存起来,需要更新的时候再更新。

        当获取 offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() 等这些全局属性时,需要此时页面上的其他元素的布局和样式处于最新状态,这会引起多次的重排和重绘,称为强制回流。

  2. 事件优化:
    • 事件委托:如果很多子元素都监听了相同的事件,可以利用事件委托机制,把原本在子元素上监听的事件委托给父元素,让父元素监听。
    • 事件稀释:对于高频触发的事件,使用防抖或节流减少执行处理函数的频率。

你可能感兴趣的:(移动端,移动端)