前端项目性能优化(全面解析)

前言

为什么要进行Web性能优化?

流量

搜索

转换率

用户体验

Amazon 发现每100ms延迟导致1%的销量损失

寻找性能瓶颈

  • 了解性能指标 - 多快才算快
  • 利用测量工具和APIs
  • 优化问题,重新测量(迭代)

性能优化指标和测量工具

行业标准

性能优化-加载
  • 理解加载瀑布图

  • 基于HAR存储于重建性能信息

  • 重要测量指标

  • 速度指数(Speed Index)4秒

  • TTFB 衡量请求到响应一共多少时间

  • 页面加载时间,页面加载完一共要用多久

  • 首次渲染 不能一直是白屏,然后在后来的某一刻突然都出来,要给用户一个渐进的体验,第一次出现内容的时间就是首次渲染很重要,让用户很快感觉到你的网站有内容出现了

性能优化-响应
  • 交互动作的反馈时间 : 交互的反馈要及时
  • 帧率FPS :60fps
  • 异步请求的完成时间:尽量在1秒内完成,完成不了加loading动画

优化模型

RAIL测量模型

什么是RAIL?

Response 响应 : 对于用户的响应

Animation 动画 : 对于用户动画的流畅

Idle 空闲 : 让浏览器有足够的空闲时间

Load 加载 : 给用户一个直观的体验

RAIL的目标

让良好的用户体验成为性能优化的目标

RAIL评估标准

相应:处理事件应在 50ms 以内完成

动画:每 10ms 产生一帧

空闲:尽可能增加空闲时间

加载:在 5s 内完成内容加载并可以交互

测量工具(需要外网)

  • Chrome DevRools 开发调试、性能评测
  • Lighthouse 网站整体质量评估
  • WebPageTest 多测试地点、全面性能报告
使用 WebPageTest 评估Web网站性能

在线进行网站分析

WebPageTest.org

waterfall chart 请求瀑布图

first view 首次访问

repeat view 二次访问

如何在本地部署WebPageTest工具

一、下载docker https://docs.docker.com/docker-for-windows/install/

二、在终端使用官方提供的docker镜像

docker pull webpagetest/server

docker pull webpagetest/agent

三、运行

docker run -d -p 4000:80 webpagetest/server

docker run -d -p 4001:80 --network=“host” -e “SERVER_URL=http://localhost:4000/work” -e “LOCATION=Test” webpagetest/agent

使用LightHouse分析性能

安装

npm install -g lighthouse

使用

lighthouse 加上你要测试的网站地址

在chrome DevTools 中使用

查看该文件有没有被使用 ctrl + shift + p

搜索Show Network request blocking

使用chrome DevTools分析性能

Audit(Lighthouse)

Throttling 调整网络吞吐

Performance 性能分析

Network 网络加载分析

性能相关APIs

常用的性能测量APIs

关键事件节点(Navigation Timing,Resource Timing)

网络状态(Network APIs)

客户端服务端协商(HTTP Client Hints) & 网页显示状态(UI APIs)

重要的时间节点

DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
First Byte时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
// 计算一些关键的性能指标
window.addEventListener('load', (event) => {
    // Time to Interactive
    let timing = performance.getEntriesByType('navigation')[0];
    console.log(timing.domInteractive);
    console.log(timing.fetchStart);
    let diff = timing.domInteractive - timing.fetchStart;
    console.log("TTI: " + diff);
})
// 观察长任务
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(entry)
    }
})

observer.observe({entryTypes: ['longtask']})
// 见面可见性的状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
    // webkit prefix detected
    vEvent = 'webkitvisibilitychange';
}

function visibilityChanged() {
    //页面不可见
    if (document.hidden || document.webkitHidden) {
        console.log("Web page is hidden.")
    } else {//页面可见
        console.log("Web page is visible.")
    }
}

document.addEventListener(vEvent, visibilityChanged, false);
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;

function updateConnectionStatus() {
  console.log("Connection type changed from " + type + " to " + connection.effectiveType);
  type = connection.effectiveType;
}

connection.addEventListener('change', updateConnectionStatus);

代码优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNgINJ8w-1624099839224)(https://z3.ax1x.com/2021/06/18/RpZTnf.png)]

V8 编译原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jH8xtzm5-1624099839228)(https://z3.ax1x.com/2021/06/18/RpehaF.png)]

抽象语法树

源码 – 抽象语法树 – 字节码Bytecode – 机器码

编译过程会进行优化

运行时可能发生反优化

V8 代表性优化机制

脚本流 :下载超过30kb时,会下载同时解析

字节码缓存 :不同页面使用相同代码会使用缓存

懒解析:不解析函数内部,用的时候解析

const {performance, PerformanceObserver} = require('perf_hooks');

const add = (a, b) => a+b;

const num1 = 1;
const num2 = 2;

performance.mark('start');

for(let i = 0; i < 10000000; i++) {
	add(num1, num2);
}

//发现类型转变会导致编译变慢
add(num1, 's');

for(let i = 0; i < 10000000; i++) {
	add(num1, num2);
}

performance.mark('end');

const observer = new PerformanceObserver((list) => {
	console.log(list.getEntries()[0]);
})
observer.observe({entryTypes: ['measure']});

performance.measure('测量1', 'start', 'end');

JavaScript优化

JavaScript 的开销和如何缩短解析时间

开销在哪里?

加载

解析&编译

执行

解决方案

Code splitting 代码拆分,按需加载

Tree shaking 代码减重

减少主线程工作量

避免长任务

避免超过 1KB 的行间脚本

使用rAF和rIC进行时间调度

Progressive Bootstrapping

可见不可交互 vs 最小可交互资源集

函数优化

函数的解析方式

  • lazy parsing 懒解析 vs eager parsing 饥饿解析

懒解析 :不解析函数内部,用的时候解析

饥饿解析:一次性都解析

//饥饿解析是在原函数在添加一队括号
export default () => {
    const add = (a, b) => a*b; // lazy parsing
    // const add = ((a, b) => a*b); // eager parsing
    const num1 = 1;
    const num2 = 2;
    add(num1, num2);
}
//当压缩的时候往往会去掉这个括号,解决方案如下
  • 利用Optimize.js优化初次加载时间(github)
对象优化

对象优化可以做哪些?

以相同顺序初始化对象成员,避免隐藏类的调整

实例化后避免添加新属性

尽量使用Array代替 array-like 对象

避免读取超过数组的长度

避免元素类型转换

/* 1 */
class RectArea { // HC0 
    constructor(l, w) {
        this.l = l; // HC1
        this.w = w; // HC2
    }
}

const rect1 = new RectArea(3,4); // 创建了隐藏类HC0, HC1, HC2
const rect2 = new RectArea(5,6); // 相同的对象结构,可复用之前的所有隐藏类



const car1 = {color: 'red'}; // HC0
car1.seats = 4; // HC1

const car2 = {seats: 2}; // 没有可复用的隐藏类,创建HC2
car2.color = 'blue'; // 没有可复用的隐藏类,创建HC3



/* 2 */
const car1 = {color: 'red'}; // In-object 属性
car1.seats = 4; // Normal/Fast 属性,存储在property store里,需要通过描述数组间接查找 



/* 3 */
Array.prototype.forEach.call(arrObj, (value, index) => { // 不如在真实数组上效率高
  console.log(`${ index }: ${ value }`);
});

const arr = Array.prototype.slice.call(arrObj, 0); // 转换的代价比影响优化小
arr.forEach((value, index) => {
  console.log(`${ index }: ${ value }`);
});



/* 4 */
function foo(array) {
  for (let i = 0; i <= array.length; i++) { // 越界比较
    if(array[i] > 1000) { // 1.沿原型链的查找 2.造成undefined与数进行比较
        console.log(array[i]); // 业务上无效、出错
    }    
  }
}



/* 5 */
const array = [3, 2, 1]; // PACKED_SMI_ELEMENTS

array.push(4.4); // PACKED_DOUBLE_ELEMENTS

ce9333be6ba56896a9b55b2a90e23f8

HTML优化

减少 iframes 使用

压缩空白符

避免节点深层级嵌套

避免使用 table 布局

删除注释

CSS&Javascript 尽量外链

删除元素默认属性

借助工具

html-minifier

CSS优化

CSS对性能的影响

样式计算开销

  • 利用DevTools测量样式计算开销
CSS优化
降低CSS对渲染的阻塞

利用GPU进行完成动画

利用contain属性

使用font-display属性

渲染优化

现代浏览器渲染原理

当用户在浏览器输入一个地址,然后按回车后到这个页面显示之前,这期间经历了那些过程
无论是 js 还是 css 都是代码(文本),计算机理解不了文本,所以第一步要做什么?

他要通过一些解释权把你这些文本翻译成他能理解的数据结构

浏览器构造对象模型

​	构造DOM对象 (内容)HTML -- DOM

​	构建CSSOM对象(样式)

​			CSS  -- CSSOM	



接下来这俩颗树会进行合并称为(Render Tree)浏览器构建渲染树



然后把真正需要留下的节点留下,不需要的去掉
关键渲染路径(critical rendering path)
JavaScript:触发视觉变化

Style:重新对样式计算

layout:布局(位置)

Paint:绘制(画在页面上)

Composite:合成(类似ps)

可优化的渲染环节和方法

布局(layouts)与绘制(paint)
渲染树只包含网页需要的节点

布局计算每个节点精确的位置和大小-“盒模型”

绘制是像素化每个节点的过程
影响回流的操作

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

  • 添加/删除元素
  • display:none
  • 移动元素位置
  • 操作styles
  • offsetLeft,scrollTop,clientWidth
  • 修改浏览器大小,字体大小
避免layout thrashing(布局抖动)
  • 避免回流
  • 读写分离
FastDom(防止布局抖动的利器 GitHub)
fastdom.measure(() => {
  console.log('measure');
});

fastdom.mutate(() => {
  console.log('mutate');
});
复合线程(compositor thread)与图层(layers)

复合线程做什么

  • 复合不会发生布局和重绘
  • 将页面拆分图层进行绘制再进行复合
  • 利用DevTools了解网页的图层拆分情况
  • 哪些样式仅影响复合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1axYLrU-1624099839236)(https://z3.ax1x.com/2021/06/18/RSb2B8.png)]

减少重绘(加上页面呈现)

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

高频 事件处理函数 防抖

如果短时间内大量触发同一事件,只会执行一次函数。

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}
React时间调度实现

资源优化

压缩&合并

为什么要压缩&合并?

减少http请求数量

减少请求资源的大小
HTML压缩
使用在线工具进行压缩 http://kangax.github.io/html-minifier/

使用html-minifier等npm工具
CSS压缩
使用在线工具进行压缩

使用  clean-css 等 npm 工具 (上面网站集成了)
JS压缩与混淆
使用在线工具进行压缩

使用webpack对JS在构建时压缩
CSS JS文件合并
若干小文件,maybe...

无冲突,服务相同的模块,ok

优化加载,NO

图片格式

图片优化的方案

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-drrsfrVC-1624099839237)(https://z3.ax1x.com/2021/06/18/RpRldI.png)]

选择一个正确地格式,不同的格式有不同的优缺点,使用特定的图片会有特定的优势

图片的大小要选择合适,不要传一个过大的图片,然后在进行尺寸大小的调整

要适配不同尺寸的屏幕

压缩:根据你的网站需求,比如摄影类需要高清的图片,压缩的空间就会很小

图片的优先级,重要的东西要优先加载

懒加载:不需要把所有的图片都加载,用户看哪些加载哪些,或者预加载

利用一些工具帮我们做这些事情
图片格式比较
JPEG/JPG的优点

https://github.com/imagemin/imagemin 压缩图片网址
我们用的最多的图片格式,它是一种有损压缩的图片格式,压缩比高,色彩保存还好,色彩感好 

JPEF/JPG的使用场景

当想使用比较大的图片还想保存展现的画质效果

JPEG/JPG的缺陷

​因为压缩比高,所以边缘会很粗糙

PNG 的优点

​可以做透明背景的图片,最大的优点是弥补了JPEG/JPG的缺点

PNG 的使用场景

​做一下小的图片,图标,logo之类

PNG 的缺陷

​体积比较大

WebP的优点

​跟PNG 有一样的质量,压缩比例比PNG 高,有兼容性问题

图片加载

图片的懒加载(lazy loading)
  • 原生的图片懒加载方案
<img src=""  loading="lazy" alt="">
//需要浏览器支持,而且自定义和扩展性不强
  • 第三方图片懒加载方案(github)

    • verlok/lazyload
    <LazyLoadImage
        className={this.props.classes.media}
        src={this.props.image}
        effect="blur"
        rel="preconnect"
    />
    
    • yall.js
    • Blazy
使用渐进式图片

解决方案

progressive-image

libjpeg

jpeg-recompress

ImageMagick

jpegtran

imagemin
使用响应式图片

我们需要在不同的屏幕上,都能适配

Srcset		属性的使用

图片集:		路径加尺寸

Sizes		属性的使用

sizes="100vw"  指的是视窗宽度的百分比

picture的使用

字体优化

什么是FOIT和FOUT

  • 字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁
  • Flash Of Invisible Text 文字从看不到到看到闪烁变化的过程
  • Flash Of Unstyled Text 开始是一种样式,后来结果渲染变为另外一种字体
使用font-display

52f526e46204795937a2e26873805b6

AJAX + Base64
  • 解决兼容问题
  • 缺点:缓存问题

构建优化

webpack的优化配置

https://webpack.js.org/configuration/mode/

Tree-shaking(调成production webpack.config.js)
上下文未用到的代码(dead code)

基于ES6 import export

f90d3ab31f2796cfc0b1e6f39f72d66

它的局限性是基于ES6,模块化的语法
但是有时候我们会修改全局作用域,可能我们在全局在添加了方法或者属性,这个时候,如果他把这个东西给摇掉了,代码就会出错

解决方案

他给我们留了后门,我们可以通过一些方式告诉webpack
//package.json 把你认为有副作用的添加到
"sideEffects": [
    "*.css"
  ]

JS压缩

Webpack 4 后引入 uglifyjs-webpack-plugin

支持 ES6 替换为terser-webpack-plugin(生产模式下默认的)

减少 JS 文件体积
作用域提升
代码体积减少

提高执行效率

同样注意Babel的modules配置
/****************** util.js ******************/
export default 'Hello,Webpack';

/**************** index.jsx ********************/
import str from './util';
console.log(str);

/***************** 没有 scope hoisting, webpack 打包后 *******************/
[
  (function (module, __webpack_exports__, __webpack_require__) {
    var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
    console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]);
  }),
  (function (module, __webpack_exports__, __webpack_require__) {
    __webpack_exports__["a"] = ('Hello,Webpack');
  })
]
/************************************/

/***************** 有 scope hoisting, webpack 打包后 *******************/
[
  (function (module, __webpack_exports__, __webpack_require__) {
    var util = ('Hello,Webpack');
    console.log(util);
  })
]
/************************************/
Babel7优化配置
在需要的地方引入 polyfill(兼容旧浏览器,做一下新的功能实现)

关于Babel默认配置的影响

辅助函数的按需引入

设置默认浏览器
//babel.config.js
module.exports = {
    presets: [
        [
            //他会把es6的语法转为别的语法(默认)
            '@babel/preset-env',
            {
                //我们希望保留 es6 语法(默认)
                modules: false,
                //设置默认浏览器
                "targets": {
                    //意思是要对超过市场份额超过百分之0.25的所有浏览器支持
                    "browsers": [">0.25%"]
                },
                 //通过这个配置就可以你需要的(polyfill)
                "useBuiltIns": "usage",
                "bugfixes": true
            }
        ],
        '@babel/preset-react'
    ],
    plugins: [
        '@babel/plugin-proposal-class-properties',
        //辅助函数的按需引入
        "@babel/plugin-transform-runtime",
    ]
};

依赖优化

无论我们的工程有多大,当我们使用webpack去打包的时候,总能得到一定的优化,但是webpack本身打包的过程有时候却有一些慢、

解决方案

noParse(不解析)
  • 提高构建速度
  • 直接通知 webpack 忽略较大的库
  • 被忽略的库不能有import,require,define的引用方式(传统方式)

8006df0a0300d655c9609393d622a6b

把我们非常确信的,不需要解析的库添加到这里就可以

DllPlugin

把我们经常需要的重复的库给提取出来,变成一种引用的方式,这样不需要每一次都重新构架,大大加速了构建的过程

  • 避免打包时对不变的库重复构建,比如我们用react 写的那么react和react dom从开始到上线可能都不会变
  • 提高构建速度
// 新建文件webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
    mode: "production",
    //入口
    entry: {
       //我希望我需要创建的文件叫react,他要包含的就是能需要创建动态链接库的类
        react: ["react", "react-dom"],
    },
    //输出
    output: {
        // name 取的是入口文件定义的名字
        filename: "[name].dll.js",
        // 路径把它放到一个叫dll的包,正常情况是没有的,正常运行之后会自动生成
        path: path.resolve(__dirname, "dll"),
        //库名称取入口名称
        library: "[name]"
    },
    plugins: [
        //我们就是通过DllPlugin帮我们去生成动态链接文件的描述文件
        new webpack.DllPlugin({
            //name和library: "[name]"一致
            name: "[name]",
            //和动态链接路径放到同样的路径下
            path: path.resolve(__dirname, "dll/[name].manifest.json")
        })
    ]
};


//运行
//在package.json下scripts中配置
"dll-build": "NODE_ENV=production webpack --config webpack.dll.config.js",
    
 npm run dll-build


// 然后回到正常的webpack配置文件中在 plugins 下添加

//引用
new DllReferencePlugin({
    manifest: require(`${__dirname}/dll/react.manifest.json`)
})

代码拆分

基于webpack的代码拆分

对应一个大型的应用来说,如果我们把所有的东西都放成一个包,那么是十分低效的,也是不可接受的,我们说如果打包是一个合的过程,那么我们现在就是拆。

webpack在默认情况下会把所有东西打成一个bundles文件(包),那我们要做的就是要吧这个包拆分成若干个小的bundles或者叫做chunks,也就是说我们要把大的东西拆分成小的东西,那么目的是什么?

缩短首屏加载时间:我们知道,如果把一个大的文件拆散了,然后把重要的东西先加载,达到一个快速的让首屏进行显示的效果的话,那么对用户的体验是一个极大的提升

Webpack 代码拆分的方法
  • 手工定义入口
// 在webpack.config.js中,添加不同的入口文件,就会打出多个包,缺点是公共部分会被多次打包
entry: {
    app: './src/index.jsx',
        // test: './src/test.js' // 测试函数lazy parsing, eager parsing
},
  • splitChunks 提取共有代码,拆分业务代码与第三方库(webpack的一个插件)
// 在webpack.config.js中有一个optimization节点,没有自行添加
optimization: {
    //我们在这个节点里直接可以使用splitChunks
        splitChunks: {
            //因为我们要给他分组,使用我们用到一个cacheGroups
            //里面我们要做俩件事,第一件事把第三方库拆分到一个bundles
            //第二件事把公共的代码拆分到一个bundles
            cacheGroups: {
                vendor: {
                    //名字
                    name: 'vendor',
                    //匹配规则 
                    test: /[\\/]node_modules[\\/]/,
                    //最小的大小
                    minSize: 0,
                    //最小多少段
                    minChunks: 1,
                    //优先级
                    priority: 10,
                    //同步加载(静态或动态的引入方式)
                    chunks: 'initial'
                },
                common: {
                    name: 'common',
                    test: /[\\/]src[\\/]/,
                    //把静态动态都考虑
                    chunks: 'all',
                    minSize: 0,
                    minChunks: 2
                }
            }
        }
    },
  • 动态加载

理解一下声明为动态加载

//静态
import { add } from './math';

console.log(add(16,26));

//动态 异步加载文件
import("./math").then(math => {
    console.log(math.add(16,26));
})

webpack提出的动态解决方案

//改变引入方式
// import Card from './Card';
// 用下面这种方式这个Card组件其实就变成了动态组件
const Card = lazy(() => import('./Card'));

for (let i = 0; i < 100; i++) {
            cards.push(model.map(panel => (
                //然后我们利用react给我们提供的Suspense,因为异步加载时有过程的,如果他还没有加载完成这个组件的时候,我们可以给他一个fallback,作为一个临时的替代
                <Suspense fallback={<div>Loading...</div>}>
                    <Card key={panel.name} image={panel.image} title={panel.name}
                          route={panel.route} description={panel.body}/>
                </Suspense>
            )));
        }

资源压缩(minification)

Terser压缩JS(上面有说)
mini-css-extract-plugin压缩CSS
要安装

mini-css-extract-plugin 提取css

optimize-css-assets-webpack-plugin
//webpack.config.js 下plugins
new MiniCssExtractPlugin({
    //提前的css文件名
    filename: '[name].[contenthash].css',
    //拆分的css文件名
    chunkFilename: '[id].[contenthash:8].css',
}),
new OptimizeCssAssetsPlugin({
    cssProcessorPluginOptions: {
        //把css的注释全部去掉
        preset: ['default', {discardComments: {removeAll: true}}],
    },
    canPrint: true
}),
  • HtmlWebpackPlugin - minify 压缩HTML

持久化缓存

持久化缓存方案
  • 每个打包的资源文件有唯一的hash值
  • 修改后只有受影响的文件hash变化
  • 充分利用浏览器缓存
//加点.[hash]即可
output: {
        path: `${__dirname}/build`,
        filename: '[name].[hash].bundle.js',
        chunkFilename: '[name].[chunkhash:8].bundle.js'
    },

监测与分析

  • Stats 分析与可视化图
  • webpack-bundle-analyzer 进行体积分析
  • speed-measure-webpack-plugin 速度分析
Webpack Chart(可视化图)
优点是在线的,不需要在项目里安装什么东西

https://alexkuz.github.io/webpack-chart/ 输入给的指令将生成的stats.json添加进去即可,我们这个图是自内向外读
source-map-explorer(体积分析)
可以进一步分析(npm安装)

"analyze": "source-map-explorer 'build/*.js'"

c76988d8909356f92d1ce18d4f1d26b

这步完成之后呢,我们就去npm run build 然后npm run analyze

speed-measure-webpack-plugin(速度分析)

npm 安装 导入

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

const smp = new SpeedMeasurePlugin();
//包裹起来
module.exports = smp.wrap({});

传输加载优化

GZip

GZip是我们用来网络资源压缩,我们来减少在网络上传输大小的技术,在网络的传输过程中进行实时的动态的压缩,GZip可以说是我们唯一可选择的技术

Nginx
http://nginx.org/en/download.html 

下载好点击打开,命令行执行 nginx 

打开浏览器,输入地址:http://localhost,访问页面,出现如下页面表示访问成功

img

接下来我的看一下我们打包完成的前段工程,我们可以看到webpack帮我们打包完成后把所有文件都放在了build目录下

//在nginx.conf 中配置 (下载nginx目录中)

server {
  # 开启gzip on为开启,off为关闭
  gzip on;
  # 检查是否存在请求静态文件的gz结尾的文件,如果有则直接返回该gz文件内容,不存在则先压缩再返回
  gzip_static on;
  # 设置允许压缩的页面最小字节数,页面字节数从header头中的Content-Length中进行获取。
  # 默认值是0,不管页面多大都压缩。
  # 建议设置成大于10k的字节数,配合compression-webpack-plugin
  gzip_min_length 10k;
  # 对特定的MIME类型生效,其中'text/html’被系统强制启用
  gzip_types text/javascript application/javascript text/css application/json;
  # Nginx作为反向代理的时候启用,开启或者关闭后端服务器返回的结果
  # 匹配的前提是后端服务器必须要返回包含"Via"的 header头
  # off(关闭所有代理结果的数据的压缩)
  # expired(启用压缩,如果header头中包括"Expires"头信息)
  # no-cache(启用压缩,header头中包含"Cache-Control:no-cache")
  # no-store(启用压缩,header头中包含"Cache-Control:no-store")
  # private(启用压缩,header头中包含"Cache-Control:private")
  # no_last_modefied(启用压缩,header头中不包含"Last-Modified")
  # no_etag(启用压缩,如果header头中不包含"Etag"头信息)
  # auth(启用压缩,如果header头中包含"Authorization"头信息)
  # any - 无条件启用压缩
  gzip_proxied any;
  # 请求加个 vary头,给代理服务器用的,有的浏览器支持压缩,有的不支持,所以避免浪费不支持的也压缩
  gzip_vary on;
  # 同 compression-webpack-plugin 插件一样,gzip压缩比(1~9),
  # 越小压缩效果越差,但是越大处理越慢,一般取中间值
  gzip_comp_level 6;
  # 获取多少内存用于缓存压缩结果,‘16  8k’表示以8k*16 为单位获得。
  # PS: 如果没有.gz文件,是需要Nginx实时压缩的
  gzip_buffers 16 8k;
  # 注:99.99%的浏览器基本上都支持gzip解压了,所以可以不用设这个值,保持系统默认即可。
  gzip_http_version 1.1;
}

重新启动nginx 服务

KeepAilve(Nginx)

HTTP KeepAilve 可以帮我们对 TCP 链接进行复用,也就是说当我们和一台服务器进行了 TCP 服务建立连接之后,接下来的请求就不需要在去重复的请求了

keepalive_timeout 0; 不适用 KeepAilve

keepalive_timeout 65; 默认 65秒不使用断开连接;

keepalive_requests 100; 计算,也就是能可以利用KeepAilve可以发送多少个请求,之后重新建立

HTTP缓存(Nginx)

我们使用 HTTP 缓存主要是为了提高重复访问时资源加载的速度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9axgIN2-1624099839244)(https://z3.ax1x.com/2021/06/19/RPVcgs.png)]

Cache-Control
第一个条件匹配的是html

Cache-Control 让是HTTP1.1的一个标准,俩个参数实际上是要告诉浏览器,能不需要在你那端缓存,如果你需要这个文件,你就去服务端进行重新获取,然后获取完去重新验证

后面俩个是为了兼容性,因为可能有老的浏览器他可能不支持



第二个条件匹配的是js和css

7天(让他从缓存里读)

 

第三类是图片和静态资源

7天(让他从缓存里读)
ETag
html资源文件的唯一标识,当我们设置缓存后实际上浏览器还是会请求服务端,然后服务端会会判断标识是否匹配,
匹配返回304 Not Modified 说明没有发生变化,不匹配就拿新数据


JSCSS 如果你不强制请求,7天之内始终都是缓存中取的

Servive Workers

作用
  • 加速重复访问
  • 离线支持
原理

d1e0e0b2f07bad5d03c356ad486e5f2

兼容性

只能在 localhost 或 https 下使用,部分浏览器不支持

HTTP/2(Nginx)(适合较高的请求量)

必须是https
# HTTPS server
#
#server {
#    listen       443 ssl;
#    server_name  localhost;
//证书
#    ssl_certificate      cert.pem;
#    ssl_certificate_key  cert.key; 

#    ssl_session_cache    shared:SSL:1m;
#    ssl_session_timeout  5m;

#    ssl_ciphers  HIGH:!aNULL:!MD5;
#    ssl_prefer_server_ciphers  on;

#    location / {
#        root   html;
#        index  index.html index.htm;
#    }
#}
SSH(自签名证书)
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048

openssl rsa -passin pass:x -in server.pass.key -out server.key 

openssl req -new -key server.key -out server.csr 

openssl x509 -req -sha256 -days 3650 -in server.csr -signkey server.key -out server.crt 
访问

https://localhost:443

会有证书验证的信息,这个时候我们什么都不用点击 直接键盘输入 thisisunsafe

HTTP2 的优势
  • 多路复用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6augAbxY-1624099839247)(https://z3.ax1x.com/2021/06/19/RPGywQ.png)]

  • 服务器推送(servers push)
 location / {
     root   html;
     index  index.html index.html	
     我们可以使用http_push通过servers push 提前推送到服务器
     http2_push /img/me0.jpg;
     http2_push /img/me1.jpg;
     http2_push /img/me2.jpg;
 }

https://z3.ax1x.com/2021/06/19/RPYibF.png

可以看到前三张没有绿色的条,也就是没有ttfb

服务端渲染SSR

SSR的好处
  • 加速首屏优化
  • 更好的SEO
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPDpBImA-1624099839250)(https://z3.ax1x.com/2021/06/19/RPtGeU.png)]

更多流行优化技术

SVG优化图标

从 PNG 到 IconFont

IconFont的优势

多个图标  ----------  一套字体,减少获取时的请求数量和体积

矢量图形,可伸缩

直接通过 CSS 修改样式(颜色,大小等)

IconFont缺点

颜色单一性
从 IconFont到 SVG

SVG优势

保持了图片能力,支持多色彩

独立的矢量图形

XML语法,搜索引擎SEO和无障碍读屏软件读取

FlexBox布局

优势

更高性能的实现方案

容器有能力决定子元素的大小,顺序,对齐,间隔等。

双向布局

预加载

优化资源加载的顺序

资源优先级

  • 浏览器默认安排资源加载优先级

为什么要调整优先级?

因为我们觉得他默认安排的优先级不合适,我们通过调整可以做到优化

怎么去调整?

使用 preload,prefetch调整优先级

// Preload:提前加载较晚出现,但对当前页面非常重要的资源
// type="font/woff2(进一步类型)" crossorigin="anonymous(字体要跨域,必须设置这个属性)"
	<link rel="preload" href="img/product2.svg地址" as="image类型" >

        
// Prefetch:提前加载后续页面需要的资源,优先级低
     <link rel="prefetch" href="img/product2.svg地址" as="image类型" >

预渲染

npm install -D react-snap

//package.json
//在打包完成之后,自动触发postbuild
"postbuild": "react-snap"

//使用ReactDOM.hydrate()

//内联样式,避免明显的FOUC(样式闪动)
预渲染的作用
  • 大型单页应用的性能瓶颈:JS下载+解析+执行

  • SSR的主要问题:牺牲TTFB来补救First Paint;实现复杂

  • Pre-rendering 打包时提前渲染页面,没有服务端参与

    窗口化提高列表性能

    Windowing(窗口化)提高列表性能

    声明是Windowing?

    我们通常一个表格里有很多行,但是我们这个表格给用户看到的时候往往是只有其中的一部分,因为我们这些行不可能都展示给用户,所以我们有一个想法,我们能不能只渲染用户看见的这些行,如果是看不到的,我们就先不渲染,如果已经看过去的我们就立即把它回收掉。

    windowing的作用
    • 加载大列表、大表单的每一行严重影响性能
    • Lazy loading仍然会让DOM 变得过大
    • windowing只渲染可见的行,渲染和滚动的性能都会提升

骨架组件

使用骨架组件减少布局移动(Layout Shift)

Skeleton/Placeholder的作用
  • 占位
  • 提升用户感知性能
  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

f(me)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值