前端性能优化(总结)

 

前言

作为一个有梦想的前端工作人员,我们不能满足于功能的完整,页面的美观,没有性能极佳的性能,自然也不会让用户眼前一亮。从用户发出请求,到呈现页面,数据经历了一个“漫长”的过程。因此,有必要先了解一下,从输入URL到页面加载完成,都发生了什么?

1、用户输入URL

2、浏览器通过DNS,把url解析为IP

3、和IP地址建立链接,发送HTTP请求

4、服务器接收请求,查库,读文件等,拼接好返回的HTTP响应

5、浏览器收到首屏HTML,开始渲染

6、解析html为dom

7、解析css为css-tree

8、dom+css生成render-tree绘图

9、加载script的js文件

10、执行js

 

所谓性能优化,就是在数据经历的每个步骤中,时间尽可能的短,所以基本有两个大方向。

1、少加载文件

  1)文件打包压缩

  2)图片格式和图片压缩

  3)缓存

  4)cdn(缩短距离,提高加载效率)

  5)ssr(首页文件执行顺序)

  6)lazy-load 懒加载

2、少执行代码

  1)大部分优化策略集中在vue或react内部

  2)长列表(本质上是对内存做优化)

    1>移动端一些机型,比如淘宝首页的无线滚动,如果直接渲染,dom过多,会崩掉,内存存储数据过多,也会崩掉。

 

DNS

1、查看dns缓存

2、本地没缓存,发起dns请求,向本地配置的DNS服务器发请求(递归)

 

优化策略:prefetch预获取,比如使用了cdn的域名

<link rel="dns-prefetch" fref="//xxx.com"></link>

 

TCP

IP TCP HTTP的关系

网络协议就是让机器聊天

1、IP协议负责寻址

2、TCP协议负责数据完整性和有序性,三次握手,慢启动,重发,粘包,滑动窗口等机制

3、HTTP应用层,负责应用层数据,数据终止时机

 

优化策略:

1、长连接

2、减少文件体积

  1)js打包压缩

  2)图片压缩

  3)gzip

3、减少文件请求次数

  1)雪碧图

  2)js,css打包

  3)缓存机制

  4)懒加载

4、减少用户和服务器的距离——cdn

5、本地存储

 

粘包

TCP有一个数据缓冲的策略。。。

 

HTTP2

二进制分帧

服务端推送

多路复用

 

缓存

1、强缓存

  1)缓存数据生效的情况下,不需要和服务器交互,直接使用;

2、弱缓存

  1)缓存数据时效了,需要问服务器,还能不能使用缓存。

 

浏览器缓存机制

通过⽹络获取内容既速度缓慢⼜开销巨⼤。较⼤的响应需要在客户端与服务器之间进⾏多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费⽤。因此,缓存并重复利⽤之前获取的资源的能⼒成为性能优化的⼀个关键⽅⾯。

广义的缓存,可以分为以下四点:

1、http cache(图片待上传)

  1)Expires和Cache-Control两个header来控制强缓存

  2)如果命中强缓存,就不会和服务器交互了,直接用缓存

  3)如果强缓存失效了,需要执行协议缓存

  4)询问文件是否修改,如果没有修改过,直接用缓存

  5)如果有etag类似文件的指纹,这个优先级更高,因为更准确

2、service worker cache

  1)Service Worker是一种独立于主线程之外的JavaScript线程。它脱离于浏览器窗体,可以实现离线缓存,网络代理等。

window.navigator.serviceWorker.register('/wbl.js').then(
    function(){
        console.log('注册成功')
    }) .catch(err => {
        console.log('注册失败')
    })

 

3、memory cache

  1)内存缓存,

4、push cache

  1)http2的缓存

 

前端缓存最佳实践

<script src="http://xx.com/xx.js"></script>

1、html使用nocache

2、文件加指纹,并且修改文件名

3、静态资源都在cdn,专门的cdn域名

  1)cdn缩减用户和服务器的距离,提升加载效率

  2)浏览器对一个域名的并发数有限制,所以用cdn域名专门加载静态资源

4、html和js,css要分开上线

  1)先上哪一个

  2)先上html,加载的js如果有缓存,使用老的js报错

  3)先上js,如果加载的js缓存失效,老的html加载了新的js报错

  4)js由于内容修改了,就生成新文件,

  5)先上静态资源(缓存设置时间比较长,有变化就是新文件,缺点是文件冗余)

  6)后上html(不设置强缓存)

cdn的内部原理

 

 

三次握手

 

 

图片优化

 

图⽚通常是最占⽤流量的,PC端加载的平均图⽚⼤⼩时600K,简直⽐js打包后的⽂件还⼤了,所以针对
图⽚的优化,也是收益不错的

 

不同的场景,使⽤不同的⽂件⾥类型

 

1、jpg

  1)有损压缩

  2)体积小,不支持透明

  3)用于背景图,轮播图

2、png

  1)无损压缩,质量高,支持透明

  2)色拍线条更丰富,小图,比如logo,商品icon

3)svg

  1)文本,体积小,矢量图

  2)渲染成本,学习成本

 

图片打包雪碧图,减少http请求次数,webpack-spritesmith

 

gzip

accept-encoding:gzip开启gzip

 

HTTP 压缩就是以缩⼩体积为⽬的,对 HTTP 内容进⾏重新编码的过程
Gzip 压缩背后的原理,是在⼀个⽂本⽂件中找出⼀些重复出现的字符串、临时替换它们,从⽽使整个⽂
件变⼩。根据这个原理,⽂件中代码的重复率越⾼,那么压缩的效率就越⾼,使⽤ Gzip 的收益也就越
⼤。反之亦然。
基本上来说,Gzip都是服务器⼲的活,⽐如nginx

 

本地存储

cookie, localstroage,  sessionStroage, indexDB

1、cookie

  1)最早,体积限定,性能浪费 ,所有请求都带上所有当前域名的cookie

2、Web Storage

  1)Local Storage 与 Session Storage

  2)存储量⼤,不⾃动发给服务端,js控制

3、indexDb

  1)运⾏在浏览器上的⾮关系型数据库

4、PWA

  1)基于缓存技术的应⽤模型

 

服务端渲染

 

SSR对于SPA性能优化比较明显

 

vue服务端渲染

const Vue = require('vue')
// 创建⼀个express应⽤
const server = require('express')()
// 提取出renderer实例
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
 // 编写Vue实例(虚拟DOM节点)
  const app = new Vue({
    data: {
    url: req.url
  },
  // 编写模板HTML的内容
  template: `<div>访问的 URL 是: {{ url }}</div>`
 })
 
 // renderToString 是把Vue实例转化为真实DOM的关键⽅法
 renderer.renderToString(app, (err, html) => {
 if (err) {
   res.status(500).end('Internal Server Error')
   return
 }
 // 把渲染出来的真实DOM字符串插⼊HTML模板中
 res.end(`
   <!DOCTYPE html>
   <html lang="en">
   <head><title>Hello</title></head>
   <body>${html}</body>
   </html>
 `)
 })
})
server.listen(8080)

 

nuxt.js服务端渲染框架

1、基于Vue.js

2、服务端

3、路由

4、热加载

5、支持http2

 

react服务端渲染

 

 

 

 

import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import App from './App'
const app = express()
// renderToString 是把虚拟DOM转化为真实DOM的关键⽅法
const RDom = renderToString(<App />)
// 编写HTML模板,插⼊转化后的真实DOM内容
const Page = `
   <html>
   <head>
   <title>test</title>
   </head>
   <body>
   <span>ssr </span>
   ${RDom}
   </body>
   </html>
 `
 
app.get('/index', function(req, res) {
   res.send(Page)
})
// 配置端⼝号
const server = app.listen(8000)

 

 

文件打包

分析文件大小

npm install lodash echarts moment -D

 

const BundleAnalyzerPlugin = require('webpack-bundleanalyzer').BundleAnalyzerPlugin;
module.exports = {
   configureWebpack: {
   plugins: [
     new BundleAnalyzerPlugin()
   ]
 }
}
import moment from 'moment'
import _ from 'lodash'
Vue.config.productionTip = false
console.log(moment())
console.log(_.max([5,4,1,6,8]))

 

删除冗余代码的tree-shaking,和去除⽆效代码,
如果是个别⻚⾯使⽤了echarts这种库,⼀定记得懒加载

 

性能监控

https://developer.mozilla.org/zh-CN/docs/Web/API/Performance

 

// 控制台

performance.getEntriesByType('navigation')

performance

挖掘瓶颈——火焰图

重定向耗时:redirectEnd - redirectStart
DNS查询耗时 :domainLookupEnd - domainLookupStart
TCP链接耗时 :connectEnd - connectStart
HTTP请求耗时 :responseEnd - responseStart
解析dom树耗时 : domComplete - domInteractive
⽩屏时间 :responseStart - navigationStart
DOMready时间 :domContentLoadedEventEnd - navigationStart
onload时间:loadEventEnd - navigationStart,也即是onload回调函数执⾏的时间。

 

LightHouse

chrome插件

命令行

npm install -g lighthouse
lighthouse https://www.kaikeba.com/ --view

 

节流

滚动,隔⼀段时间只触发⼀次 第⼀个⼈说了算 在时间结束出发
 
// func是⽤户传⼊需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {
   // 上⼀次执⾏该函数的时间
   let lastTime = 0
   return (...args)=>{
      // 当前时间
      let now = +new Date()
      // 将当前时间和上⼀次执⾏函数时间对⽐
      // 如果差值⼤于设置的等待时间就执⾏函数
      if (now - lastTime > wait) {
         lastTime = now
         func.apply(this, args)
      }
 }
}
setInterval(
    throttle(() => {
       console.log(1)
    }, 500),
 1 )

 

防抖

输⼊,完成后再统⼀发送请求,最后⼀个⼈说了算 只认最后⼀次
 
// func是⽤户传⼊需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
    // 缓存⼀个定时器id
    let timer = 0
    // 这⾥返回的函数是每次⽤户实际调⽤的防抖函数
    // 如果已经设定过定时器了就清空上⼀次的定时器
    // 开始⼀个新的定时器,延迟执⾏⽤户传⼊的⽅法
    return function(...args) {
       if (timer) clearTimeout(timer)
       timer = setTimeout(() => {
       func.apply(this, args)
    }, wait)
 }
}

dom

!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>DOM操作测试</title>
</head> 
<body> <div id="container"></div> </body> </html>

 

重绘与回流

回流:当我们对 DOM 的修改引发了 DOM ⼏何尺⼨的变化(⽐如修改元素的宽、⾼或隐藏元素等)
时,浏览器需要重新计算元素的⼏何属性(其他元素的⼏何属性和位置也会因此受到影响),然后再将
计算的结果绘制出来。这个过程就是回流(也叫重排)。
 
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其⼏何属性(⽐如修改了颜⾊或背景⾊)
时,浏览器不需重新计算元素的⼏何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环
节)。这个过程叫做重绘。
 
由此我们可以看出,重绘不⼀定导致回流,回流⼀定会导致重绘。
 
回流是影响最大的
1、窗体,字体大小
2、增加样式表
3、内容变化
4、class属性
5、offsetWidth和offsetHeight
6、fixed
 
优化策略: DocumentFragment缓存dom
 

lazy-load

// 获取所有的图⽚标签
 const imgs = document.getElementsByTagName('img')
 // 获取可视区域的⾼度
 const viewHeight = window.innerHeight ||
document.documentElement.clientHeight
 // num⽤于统计当前显示到了哪⼀张图⽚,避免每次都从第⼀张图⽚开始检查是否露出
 let num = 0
 function lazyload(){
 for(let i=num; i<imgs.length; i++) {
    // ⽤可视区域⾼度减去元素顶部距离可视区域顶部的⾼度
    let distance = viewHeight - imgs[i].getBoundingClientRect().top
    // 如果可视区域⾼度⼤于等于元素顶部距离可视区域顶部的⾼度,说明 
  元素露出
     if(distance >= 0 ){
       // 给元素写⼊真实的src,展示图⽚
       imgs[i].src = imgs[i].getAttribute('data-src')
       // 前i张图⽚已经加载完毕,下次从第i+1张开始检查是否露出
       num = i + 1
    }
   }
 }
 // 监听Scroll事件
 window.addEventListener('scroll', lazyload, false);

 

vue相关优化

v-if vs v-show

初始性能 VS 频繁切换性能

 

和渲染无关的数据,不要放在data上

data也不要嵌套过多层

 

nextTick

修改数据的当下,视图不会⽴刻更新,⽽是等同⼀事件循环中的所有数据变化完成之后,再统⼀进⾏视
图更新
 
this.$nextTick(function() {
    // DOM 更新了
 
 })

 

Object.freeze()

冻结数据,取消setters

 

路由懒加载

const router = new VueRouter({
   routes: [
      { path: '/foo', component: () => import('./Foo.vue') }
   ]
})

 

keep-alive缓存页面

<template>
   <div id="app">
      <keep-alive>
         <router-view/>
      </keep-alive>
   </div>
</template>

 

使用v-show复用dom

<template>
   <div class="cell">
   <!--这种情况用v-show复用DOM,比v-if效果好-->
      <div v-show="value" class="on">
      <Heavy :n="10000"/>
   </div>
   <section v-show="!value" class="off">
      <Heavy :n="10000"/>
   </section>
  </div>
</template>

 

<template>
   <ul>
      <li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
   </ul>
</template>
<script>
export default {
   computed: {
      activeUsers: function () {
         return this.users.filter(function (user) {
            return user.isActive
      })
   }
}
}
</script>

 

长列表性能优化

如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化

export default {
   data: () => ({
      users: []
   }),
   async created() {
      const users = await axios.get("/api/users");
      this.users = Object.freeze(users);
   }
};

 

如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容

<recycle-scroller class="items" :items="items" :item-size="24">
   <template v-slot="{ item }">
      <FetchItemView :item="item" @vote="voteItem(item)"/>
   </template>
</recycle-scroller>

 

参考vue-virtual-scroller、vue-virtual-scroll-list

 

事件的销毁

Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。

created() {
   this.timer = setInterval(this.refresh, 2000)
},
beforeDestroy() {
   clearInterval(this.timer)
}

 

图片懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域 内的图片先不做加载, 等到滚动到可视区域后再去加载。

<img v-lazy="/static/img/1.png">

参考项目:vue-lazyload

 

第三方插件按需引入

像element-ui这样的第三方组件库可以按需引入避免体积太大。

import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)

 

无状态的组件标记为函数式组件

<template functional>
   <div class="cell">
      <div v-if="props.value" class="on"></div>
      <section v-else class="off"></section>
   </div>
</template>
<script>
export default {
   props: ['value']
}
</script>

 

子组件分割

<template>
   <div>
      <ChildComp/>
   </div>
</template>
<script>
export default {
   components: {
      ChildComp: {
         methods: {
             heavy () { /* 耗时任务 */ }
         },
         render (h) {
            return h('div', this.heavy())
         }
      }
   }
}
</script>

 

变量本地化

<template>
   <div :style="{ opacity: start / 300 }">
      {{ result }}
   </div>
</template>
<script>
import { heavy } from '@/utils'
export default {
   props: ['start'],
   computed: {
      base () { return 42 },
      result () {
         const base = this.base // 不要频繁引用this.base
         let result = this.start
         for (let i = 0; i < 1000; i++) {
            result += heavy(base)
         }
         return result
      }
   }
}
</script>

 

 

react

只传递需要的props

需要随便<Component{...props}>

 

 

key

无状态组件

 

pureComponent shouldComponentUpdate

渲染时机

 

少在render中绑定事件

 

redux + reselect

data扁平化

 

每当store发⽣改变的时候,connect就会触发重新计算,为了减少重复的不必要计算,减少⼤型项⽬的
性能开⽀,需要对selector函数做缓存。推荐使⽤reactjs/reselect, 缓存的部分实现代码如下。

 

长列表 react + virtualized

只维护上下三屏的dom,模拟滚动

renderRow(item) {
 return (
    <div key={item.id} className="row">
     <div className="image">
        <img src={item.image} alt="" />
     </div>
     <div className="content">
        <div>{item.name}</div>
        <div>{item.text}</div>
     </div>
     </div>
 );
}

 

如果有1000个, 只渲染20个,⿏标滚动的时候,新节点替换⽼节点
import { List } from "react-virtualized"; <div className="list">
   {this.list.map(this.renderRow.bind(this))}
</div>
// 改为
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;

<div className="list">
 <List
    width={rowWidth}
     height={listHeight}
     rowHeight={rowHeight}
     rowRenderer={this.renderRow.bind(this)}
     rowCount={this.list.length} />
</div>
renderRow({ index, key, style }) {
 return (
     <div key={key} style={style} className="row">
     <div className="image">
         <img src={this.list[index].image} alt="" />
     </div>
     <div className="content">
         <div>{this.list[index].name}</div>
         <div>{this.list[index].text}</div>
 </div>
 </div>
 );
}

 

 

Web Workers

 

 

浏览器渲染

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

雅虎军规

尽量减少 HTTP 请求个数——须权衡
使⽤ CDN(内容分发⽹络)
为⽂件头指定 Expires 或 Cache-Control ,使内容具有缓存性。
避免空的 src 和 href
使⽤ gzip 压缩内容
把 CSS 放到顶部
把 JS 放到底部
避免使⽤ CSS 表达式
将 CSS 和 JS 放到外部⽂件中
减少 DNS 查找次数
精简 CSS 和 JS
避免跳转
剔除重复的 JS 和 CSS
配置 ETags
使 AJAX 可缓存
尽早刷新输出缓冲
使⽤ GET 来完成 AJAX 请求
延迟加载
预加载
减少 DOM 元素个数
根据域名划分⻚⾯内容
尽量减少 iframe 的个数
避免 404
减少 Cookie 的⼤⼩
使⽤⽆ cookie 的域
减少 DOM 访问
开发智能事件处理程序
⽤ 代替 @import
避免使⽤滤镜
优化图像
优化 CSS Spirite
不要在 HTML 中缩放图像——须权衡
favicon.ico要⼩⽽且可缓存
保持单个内容⼩于25K
打包组件成复合⽂本

 

posted @ 2020-05-05 15:08  自由与未来  阅读(656)  评论(0)    收藏  举报