前端性能优化

从输入URL到页面加载完成,发生了什么?

  1. 用户输入baidu.com
  2. 浏览器通过DNS,把ur解析为IP
  3. IP地址建立TCP链接发送HTTP请求
  4. 服务器接收请求,查库,读文件等,拼接好返回的HTTP响应
  5. 浏览器收到首屏html,开始渲染
  6. 解析html为dom
  7. 解析css为css-tree
  8. dom+css生成render-tree绘图
  9. 加载script的js文件
  10. 执行js

所谓性能优化,就是上面的步骤加一起,时间尽可能的短,基本是两大方向

  1. 少加载文件
  2. 少执行代码

前端的性能优化

  • 文件获取优化
  1. 加载文件 Css Js
  2. http协议的细节
  3. 从输入url到页面渲染完毕,发生了什么
  4. 大厂怎么上线前端代码的
  • 代码执行优化
  1. 节流防抖
  2. 重绘回流
  3. vue react(sr)常见优化代码执行的更少,dom操作的更少
  4. 浏览器是如何渲染页面的
  5. vue源码做过哪些执行层面的优化

一、文件获取优化

DNS

  • 查看dns缓存
  • 本地没缓存,发起dns请求,向本地配置的DNS服务器发请求(递归)
  • 优化:prefetch预获取,比如使用了cdn的域名
link rel="dns-prefetch" href="//tce.alicdn.com"/>
link rel="dns-prefetch" href="//gm.mmstat.com"/>
link rel="dns-prefetch" href="//avc.alicdn.com"/>
link rel="dns-prefetch" href="//img.mmstat.com"/>
<!--当前页面所有用到的域名都加上-->

Pa!Image-20190403182525979](https://ws4.sinaimg.cn/large/006tkft
clylglpmcr2uemj31m00gogue. jpg)

网络协议

  • ip协议(寻址)479570.218
  • ip协议之上,使用tcp来确保数据的完整有序
  1. 三次握手
  2. 滑动窗口
  3. 慢启动
  4. 挥手
  5. 分包
  6. 重发
  • tcp协议之上,我们使用http协办议来作为网页传输的协议应用层)

DNS预加载

  • 如何少加载文件——合理利用浏览器文件缓存

main.js的加载来看待这个问题

  • 首次加载http请求server正常返回
  1. 返回响应头加上强缓存的说明(真是事件)
  2. expires:wed11Aug201928:50:90(过期实践)
  3. cache-control:max-age=3000000(httpl.1精准,优先级高)
  4. 两个 header都是后端告诉浏览器,这个文件,多少时间内不过期(比如1个小时)
  5. 浏览器接收到上面两个 header就会文件保存起来
  • 1个小时内再请求这个文件
  1. 浏览器识别到强缓存命中,请求不发出,直接用本地的缓存文件状态码是200 from cache
  • 2个小时后,在此请求这个文件,强缓存失效,使用协商缓存

      浏览器不会直接发出请求,而是问一下后端, header带上请求头

  1. if- modified- since:日期,后端小老弟,这个文件,在这个日子之后有没有修改过
  2. 后端告诉你没改过,请用缓存相应是304 not modified
  3. 浏览器直接用缓存
  4. 优先级更高的,是etag,文件的指纹,内容不变、指纹不变
  • 如果后端告诉你改过了只能重新加载了

如何高效利用缓存?如何上线前端代码?

  • 缓存时间过长——发布上线了,用户端还用缓存,会有bug
  • 缓存时间过短——重复加载文件太多,浪费带宽

模板(html)、静态资源( css js image video audio等)

  • 模板或者html不能有缓存

                这个是入口,我们一旦有新代码发布上线,没法加载了

  • 文件加哈希

                上线之后,要求用户抢刷新这种问题用文件名+指纹的方式解决了

                a.hash.js,hash是整个a.js文件的md5值,文件内容不变,hash不变,缓存生效

  • 文件内容变,缓存变,文件名都变了肯定要重新加载
  •  a.abcd.js => a.ghul.js
  • 所有静态资源,缓存可以设置的贼长
  1. 加时间戳< script src="/a,js?_t=xxx">
  2. 加版本号< script src="/a,js?v=1,6">比如 jquery vue共用库,内容没改,还需要重新加载
  3. 加指纹但是不产生新文件< script src="/a.js?h=abcd12sa">这种方式,不能清楚cdn的缓存,但是不生成新文件,会有小问题(htm和js先上线哪个)
  4. 最终诞生了最优的产生新文件< script src="a.abcd12sa.js">先上线js,再上线html
  5. webpack build

TCP

  • IP、TCP、HTTP的关系
  1. IP负责找到
  2. TCP负责数据完整性和有序型,三次握手,粘包,滑动窗口等机制
  3. http应用层,负责应用层数据,数据终止时机
  • 优化策略
  1. 长连接
  2. 减少文件体积(打包压缩、图片压缩、gzip)
  3. 减少文件请求次数(雪碧图、js、css打包、缓存控制、懒加载)
  4. 减少用户和服务器的距离(CDN)
  5. 本地存储

浏览器缓存机制

  • 通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用。因此,缓存井重复利用之前获取的资源的能力成为性能优化的一个关键方面。
  • 广义的缓存,可以分为Http Cache、Service Worker Cache、Memory Cache、Push Cache

Http Cache

浏览器大佬:需要获取 main.js,看下强缓存里有么有

  • Expires和 Cache-Contro两个 header来控制强缓存
expires:Wed 11 Mar 2019 16:12:18GMT
cache-control:max-age=31536000/1.1  // 精准、优先级高
  • 如果命中强缓存,就不会和服务器交互了,直接用缓存;如果强缓存失效了,需要执行协商缓存
  1. 服务器:小老弟,浏览器大佬需要 main.js这个文件上次修改:If-Modified-Since: Fri 27 oct 2017 06: 35: 57GMT
  2. 服务器:小老弟,没改过,直接用缓存把这次请求返回304 not Modified

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

ETag:W/"2aaa-1298921459"
If-None-Match: W/"2aaa-129892f459"

Memory Cache

内存缓存,短命比如常用数据存j里,浏览器也有自己的策略,base64图片,体积小的静态资源

Service Worker Cache

  • Service Worker是一种独立于主线程之外的 Javascript线程。它脱离于浏览器窗体,算是幕后工作,可以实现离线缓存,网络代理等
window.navigator.serviceworker.register('/aaa.js')
.then(() => { console.log("注册成功")})
.catch(err => console.error("注册失败"))

Push Cache(http2的缓存)

  • 文件打包——分析文件大小
  • 如果个别页面使用了echarts这种库,一定要记得懒加载

npm install lodash echarts moment -D

const BundleAnalyzerPLugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module exports = {
    configurewebpack:{
        plugins:[
            new BundleAnalyzerPlugin()
        ]
    }
}

import moment from 'moment'
import from 'lodash'
Vue.config.productionTip = false
console.log(moment)
console.log(_max([3,7,4,8,0]))

图片优化

  • 图片通常是最占用流量的,PC端加载的平均图片大小时600K,简直比打包后的文件还大了,所以针对图片的优化,也是收益不错的不同的场景,使用不同的文件里类型。
  • jpg
  1. 有损压缩
  2. 体积小不支持透明
  3. 用于背景图,轮播图
  • png
  1. 无损压缩,质量高,支持透明
  2. 色彩线条更丰富,小图,比如ogo,商品icon
  • svg
  1. 文本,体积小矢量图
  2. 渲染成本,学习成本
  • 图片打包雪碧图、减少htp请求次数、webpack-spritesmith

gzip

  • HTTP压缩就是以编小体积为目的,对HTTP内容进行重新编码的过程Gzip压缩背后的原理,是在一个文本文件中找出一些重复出现的字符串、临时替换它们,从而使整个文件变小。根据这个原理,文件中代码的重复率越高,那么压缩的效率就越高,使用Gzip的收益也就越大,反之亦然。
  • 基本上来说,Gzip都是服务器干的活,比如Nginx
accept-encoding:gzip  // 开启gzip

本地存储cookie、localstroage、session Stroage、indexDB

  • cookie——最早,体积先定,性能浪费,所有请求都带上所有当前域名的cookie
  • Web Storage——Local Storage、Session Storage、存储量大,不自动发给服务端,js控制
  • indexdb——运行在浏览器上的非关系型数据库
  • PWA——基于缓存技术的应用模型

CDN

  • 海南的哥们,访问开课吧,光电线就要那么远,肯定慢,所以我们可以吧静态资源,部署在分布式的cdn上,海南的哥们,就近获取资源,比如广州机房
  • cdn单独的域名,浏览器并发获取

服务端渲染

  • 如果是SPA首屏SSR就是性能优化的重要一环
  • react服务端渲染
import express from express
import React from 'react'
import renderTostring from 'react-dom/server'
import App from './App'

const app = express()
// renderTostring是把拟D0N转化为真实D0同的关键方法
const RDom = renderToString(<App/>);
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)

二、代码执行优化

性能监控Performance

performance.getEntriesByType('navigation')

  1. 重定向耗时: redirectEnd- redirectStart
  2. DNS查询耗时: domainLookupEnd- domainLookupStart
  3. TCP链接耗时: connectEnd- connectstart
  4. HTTP请求耗时: responseEnd-responseStart
  5. 解析dom树耗时: domComplete-domInteractive
  6. 白屏时间: responseStart- navigationStart
  7. DOMready时间: domContentLoadedEventEnd-navigationStart
  8. onload时间: loadEventEnd- navigationStart,也即是 onload回调函数执行的时间。

节流

滚动隔一段时间只触发一次,第一个人说了算,在时间结束触发

应用场景:(图片懒加载……)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    <style>
        #hhh {
            width: 300px;
            height: 2000px;
            background-color: brown;
        }
    </style>
</head>
<body>
    <div id="hhh">123456</div>
    <script>
        let i = 1
        const throttle = (fn, delay = 500) => {
            let lastTime = 0
            return (...args) => {
                let nowTime = Date.now()
                if (nowTime - lastTime > delay) {
                    fn.apply(this, args)
                    lastTime = nowTime
                }
            }
        }
        window.addEventListener('scroll',
            throttle(() => {
                console.log(i)
                i+=1
            }))
    </script>
</body>
</html>

防抖

输入完成后统一发送请求,最后一个人说了算,只认最后一次

应用场景:(向后端发起请求校验用户名是否重复……)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
    <style>
        #hhh {
            width: 300px;
            height: 2000px;
            background-color: brown;
        }
    </style>
</head>
<body>
    <div id="hhh">123456</div>
    <script>
        let i = 1
        const debounce = (fn, delay = 500) => {
            let timer = 0
            return (...args) => {
                if (timer) {
                    clearTimeout(timer)
                }                
                timer = setTimeout(() => {
                    fn.apply(this, args)
                }, delay)
            }
        }
        window.addEventListener('scroll',
            debounce(() => {
                console.log(i)
                i+=1
            }))
    </script>
</body>
</html>

重绘回流

回流:当我们对DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。

重绘:当我们对DOM的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘,由此我们可以看出,重绘不一定导致回流,回流一定会导致重绘。

回流是影响最大的

  1. 窗体,字体大小
  2. 增加样式表
  3. 内容变化
  4. class属性
  5. offfsetWidth、offsetHeight
  6. fixed


DocumentFragment缓存dom

lazyLoad

// 获取所有的图片标签
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) {
            imgs[i].src = imgs[i].getAttribute('data-src')
            //前张圈片已经加完毕,下次从第1+1张开始信童是否塞出
            num = i + 1
        }
    }
}

// 监听scroll事件
window.addEventListener('scroll', lazyLoad, false)

长列表react-virtualized

vue的dom做了那些优化?

  • vue中,使用definePropery实际上是能够知道所有数据的修改,知道哪个数据被修改了,然后直接去修改dom(vue就是这么做的)
  • 虚拟dom是什么?数据修改后,我们通过dom diff算出哪个数据给修改了,然后再去修改dom
  • 我们有definePropery为啥还需要虚拟dom?
  • vue的问题是什么?每个数据都有监听器, watcher太多了,项目庞大之后,尤其明显,vue2做了watcher只到组件层,一个组件只有一个watcher,组件内部使用dom diff
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值