流量
搜索
转换率
用户体验
Amazon 发现每100ms延迟导致1%的销量损失
寻找性能瓶颈
理解加载瀑布图
基于HAR存储于重建性能信息
重要测量指标
速度指数(Speed Index)4秒
TTFB 衡量请求到响应一共多少时间
页面加载时间,页面加载完一共要用多久
首次渲染 不能一直是白屏,然后在后来的某一刻突然都出来,要给用户一个渐进的体验,第一次出现内容的时间就是首次渲染很重要,让用户很快感觉到你的网站有内容出现了
什么是RAIL?
Response 响应 : 对于用户的响应
Animation 动画 : 对于用户动画的流畅
Idle 空闲 : 让浏览器有足够的空闲时间
Load 加载 : 给用户一个直观的体验
让良好的用户体验成为性能优化的目标
相应:处理事件应在 50ms 以内完成
动画:每 10ms 产生一帧
空闲:尽可能增加空闲时间
加载:在 5s 内完成内容加载并可以交互
在线进行网站分析
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
安装
npm install -g lighthouse
使用
lighthouse 加上你要测试的网站地址
在chrome DevTools 中使用
查看该文件有没有被使用 ctrl + shift + p
搜索Show Network request blocking
Audit(Lighthouse)
Throttling 调整网络吞吐
Performance 性能分析
Network 网络加载分析
关键事件节点(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);
源码 – 抽象语法树 – 字节码Bytecode – 机器码
编译过程会进行优化
运行时可能发生反优化
脚本流 :下载超过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');
开销在哪里?
加载
解析&编译
执行
解决方案
Code splitting 代码拆分,按需加载
Tree shaking 代码减重
减少主线程工作量
避免长任务
避免超过 1KB 的行间脚本
使用rAF和rIC进行时间调度
Progressive Bootstrapping
可见不可交互 vs 最小可交互资源集
函数的解析方式
懒解析 :不解析函数内部,用的时候解析
饥饿解析:一次性都解析
//饥饿解析是在原函数在添加一队括号
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);
}
//当压缩的时候往往会去掉这个括号,解决方案如下
对象优化可以做哪些?
以相同顺序初始化对象成员,避免隐藏类的调整
实例化后避免添加新属性
尽量使用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
减少 iframes 使用
压缩空白符
避免节点深层级嵌套
避免使用 table 布局
删除注释
CSS&Javascript 尽量外链
删除元素默认属性
html-minifier
样式计算开销
降低CSS对渲染的阻塞
利用GPU进行完成动画
利用contain属性
使用font-display属性
无论是 js 还是 css 都是代码(文本),计算机理解不了文本,所以第一步要做什么?
他要通过一些解释权把你这些文本翻译成他能理解的数据结构
浏览器构造对象模型
构造DOM对象 (内容)
HTML -- DOM
构建CSSOM对象(样式)
CSS -- CSSOM
接下来这俩颗树会进行合并称为(Render Tree)浏览器构建渲染树
然后把真正需要留下的节点留下,不需要的去掉
JavaScript:触发视觉变化
Style:重新对样式计算
layout:布局(位置)
Paint:绘制(画在页面上)
Composite:合成(类似ps)
渲染树只包含网页需要的节点
布局计算每个节点精确的位置和大小-“盒模型”
绘制是像素化每个节点的过程
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。
fastdom.measure(() => {
console.log('measure');
});
fastdom.mutate(() => {
console.log('mutate');
});
复合线程做什么
当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) // 进入该分支说明当前并没有在计时,那么就开始一个计时
}
}
}
为什么要压缩&合并?
减少http请求数量
减少请求资源的大小
使用在线工具进行压缩 http://kangax.github.io/html-minifier/
使用html-minifier等npm工具
使用在线工具进行压缩
使用 clean-css 等 npm 工具 (上面网站集成了)
使用在线工具进行压缩
使用webpack对JS在构建时压缩
若干小文件,maybe...
无冲突,服务相同的模块,ok
优化加载,NO!
选择一个正确地格式,不同的格式有不同的优缺点,使用特定的图片会有特定的优势
图片的大小要选择合适,不要传一个过大的图片,然后在进行尺寸大小的调整
要适配不同尺寸的屏幕
压缩:根据你的网站需求,比如摄影类需要高清的图片,压缩的空间就会很小
图片的优先级,重要的东西要优先加载
懒加载:不需要把所有的图片都加载,用户看哪些加载哪些,或者预加载
利用一些工具帮我们做这些事情
JPEG/JPG的优点
https://github.com/imagemin/imagemin 压缩图片网址
我们用的最多的图片格式,它是一种有损压缩的图片格式,压缩比高,色彩保存还好,色彩感好
JPEF/JPG的使用场景
当想使用比较大的图片还想保存展现的画质效果
JPEG/JPG的缺陷
因为压缩比高,所以边缘会很粗糙
PNG 的优点
可以做透明背景的图片,最大的优点是弥补了JPEG/JPG的缺点
PNG 的使用场景
做一下小的图片,图标,logo之类
PNG 的缺陷
体积比较大
WebP的优点
跟PNG 有一样的质量,压缩比例比PNG 高,有兼容性问题
<img src="" loading="lazy" alt="">
//需要浏览器支持,而且自定义和扩展性不强
第三方图片懒加载方案(github)
<LazyLoadImage
className={this.props.classes.media}
src={this.props.image}
effect="blur"
rel="preconnect"
/>
解决方案
progressive-image
libjpeg
jpeg-recompress
ImageMagick
jpegtran
imagemin
我们需要在不同的屏幕上,都能适配
Srcset 属性的使用
图片集: 路径加尺寸
Sizes 属性的使用
sizes="100vw" 指的是视窗宽度的百分比
picture的使用
什么是FOIT和FOUT
https://webpack.js.org/configuration/mode/
上下文未用到的代码(dead code)
基于ES6 import export
它的局限性是基于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);
})
]
/************************************/
在需要的地方引入 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本身打包的过程有时候却有一些慢、
解决方案
把我们非常确信的,不需要解析的库添加到这里就可以
把我们经常需要的重复的库给提取出来,变成一种引用的方式,这样不需要每一次都重新构架,大大加速了构建的过程
// 新建文件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在默认情况下会把所有东西打成一个bundles文件(包),那我们要做的就是要吧这个包拆分成若干个小的bundles或者叫做chunks,也就是说我们要把大的东西拆分成小的东西,那么目的是什么?
缩短首屏加载时间:我们知道,如果把一个大的文件拆散了,然后把重要的东西先加载,达到一个快速的让首屏进行显示的效果的话,那么对用户的体验是一个极大的提升
// 在webpack.config.js中,添加不同的入口文件,就会打出多个包,缺点是公共部分会被多次打包
entry: {
app: './src/index.jsx',
// test: './src/test.js' // 测试函数lazy parsing, eager parsing
},
// 在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>
)));
}
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
}),
//加点.[hash]即可
output: {
path: `${__dirname}/build`,
filename: '[name].[hash].bundle.js',
chunkFilename: '[name].[chunkhash:8].bundle.js'
},
优点是在线的,不需要在项目里安装什么东西
https://alexkuz.github.io/webpack-chart/ 输入给的指令将生成的stats.json添加进去即可,我们这个图是自内向外读
可以进一步分析(npm安装)
"analyze": "source-map-explorer 'build/*.js'"
这步完成之后呢,我们就去npm run build 然后npm run analyze
npm 安装 导入
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
//包裹起来
module.exports = smp.wrap({});
GZip是我们用来网络资源压缩,我们来减少在网络上传输大小的技术,在网络的传输过程中进行实时的动态的压缩,GZip可以说是我们唯一可选择的技术
http://nginx.org/en/download.html
下载好点击打开,命令行执行 nginx
打开浏览器,输入地址:http://localhost,访问页面,出现如下页面表示访问成功
接下来我的看一下我们打包完成的前段工程,我们可以看到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 服务
HTTP KeepAilve 可以帮我们对 TCP 链接进行复用,也就是说当我们和一台服务器进行了 TCP 服务建立连接之后,接下来的请求就不需要在去重复的请求了
keepalive_timeout 0; 不适用 KeepAilve
keepalive_timeout 65; 默认 65秒不使用断开连接;
keepalive_requests 100; 计算,也就是能可以利用KeepAilve可以发送多少个请求,之后重新建立
我们使用 HTTP 缓存主要是为了提高重复访问时资源加载的速度
第一个条件匹配的是html
Cache-Control 让是HTTP1.1的一个标准,俩个参数实际上是要告诉浏览器,能不需要在你那端缓存,如果你需要这个文件,你就去服务端进行重新获取,然后获取完去重新验证
后面俩个是为了兼容性,因为可能有老的浏览器他可能不支持
第二个条件匹配的是js和css
7天(让他从缓存里读)
第三类是图片和静态资源
7天(让他从缓存里读)
html资源文件的唯一标识,当我们设置缓存后实际上浏览器还是会请求服务端,然后服务端会会判断标识是否匹配,
匹配返回304 Not Modified 说明没有发生变化,不匹配就拿新数据
JS 和 CSS 如果你不强制请求,7天之内始终都是缓存中取的
只能在 localhost 或 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;
# }
#}
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
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;
}
可以看到前三张没有绿色的条,也就是没有ttfb
IconFont的优势
多个图标 ---------- 一套字体,减少获取时的请求数量和体积
矢量图形,可伸缩
直接通过 CSS 修改样式(颜色,大小等)
IconFont缺点
颜色单一性
SVG优势
保持了图片能力,支持多色彩
独立的矢量图形
XML语法,搜索引擎SEO和无障碍读屏软件读取
优势
更高性能的实现方案
容器有能力决定子元素的大小,顺序,对齐,间隔等。
双向布局
资源优先级
为什么要调整优先级?
因为我们觉得他默认安排的优先级不合适,我们通过调整可以做到优化
怎么去调整?
使用 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?
我们通常一个表格里有很多行,但是我们这个表格给用户看到的时候往往是只有其中的一部分,因为我们这些行不可能都展示给用户,所以我们有一个想法,我们能不能只渲染用户看见的这些行,如果是看不到的,我们就先不渲染,如果已经看过去的我们就立即把它回收掉。
使用骨架组件减少布局移动(Layout Shift)