https://mp.weixin.qq.com/s/xccL9lTY6GKY2YqX7_FDQw
1、服务开启 gzip
为了减少数据在网络上的传输时间,可以启用gzip压缩。gzip压缩是属于时间换空间的做法
具体是否开启,视自己项目情况而定
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
gzip_disable "MSIE [1-6]\.";
2、前端打包开启gzip
如果 静态资源服务 没有开启
gzip的话 ,前端在打包的时候,可以开启gzip,这样在打包的文件中会生成.gz的文件
配置如下:
// webpack.build.js
// 开启 gzip 压缩
const CompressionWebpackPlugin = require("compression-webpack-plugin");
// gzip 压缩匹配规则
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
...,
plugins: [
config.build.productionGzip && new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
}),
]
}
3、前端打包分析第三方包体积
webpack-bundle-analyzer可以分析出打包之后 各个模块所占用的体积,方便我们知道,项目中哪些模块占得体积大,哪些模块不怎么占体积配置以下命令之后 就可以运行
npm run analyze查看分析结果
配置如下:
// webpack.build.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
...,
plugins: [
// 打开打包结果分析工具
process.env.npm_config_report && new BundleAnalyzerPlugin(),
]
}
// package.json
{
...,
"scripts": {
"analyze": "cross-env npm_config_report=true npm run build",
},
...,
}
4、前端进行包抽离
总所周知,我们
npm i到node_module的包,如果没有进行按需导入的话,大概率,会被打包到vendor.js中,这也就导致vendor.js弄不好 会有好几兆,这样一个大体量的入口文件会导致首次渲染白屏时间过长.所以,需要将不怎么需要更新的包,在打包的时候,进行抽离。 这样。在 HTML 中,可以使用引入 CDN 加速之后的 npm 包资源
配置如下
// webpack.base.js
module.exports = {
externals: { // 使用外链引入的npm包
jszip: 'JSZip',
xlsx: 'XLSX',
'text-encoding': 'TextEncodingPolyfill'
},
}
5、H5图片优化
5.1 CDN
如果有 CDN 图床服务,可以开启CDN 图床尺寸大小压缩功能
然后利用 img 标签的
srcset/sizes属性和picutre标签实现响应式图就可以实现,在不同分辨率下,加载不同像素的图片
具体做法:
// srcset 可以接受一段字符串,用来定义一个或多个图像候选地址,以 ,分割,每个候选地址将在特定条件下得以使用。
// 候选地址包含图片 URL 和一个可选的宽度描述符和像素密度描述符,该候选地址用来在特定条件下替代原始地址成为 src 的属性
<div class="box">
<img src="/files/16797/clock-demo-200px.png"
alt="Clock"
srcset="/files/16864/clock-demo-200px.png 1x, /files/16797/clock-demo-400px.png 2x">
</div>
5.2 非CDN
处理方式主要是要控制好图片懒加载的逻辑(如
onload后再加载),可以借助各类lazyload的库去实现。H5项目用的是位置检测(getBoundingClientRect)图片到达页面可视区域再展示。
简易的纯原生懒加载实现:
function lazyLoad(){
const imageToLazy = document.querySelectorAll('img[data-src]');
const loadImage = function (image) {
image.setAttribute('src', image.getAttribute('data-src'));
image.addEventListener('load', function() {
image.removeAttribute("data-src");
})
}
const intersectionObserver = new IntersectionObserver(function(items, observer) {
items.forEach(function(item) {
if(item.isIntersecting) {
loadImage(item.target);
observer.unobserve(item.target);
}
});
});
imageToLazy.forEach(function(image){
intersectionObserver.observe(image);
})
}
6、前端工程化优化 css
很多时候,一个css文件,首屏需要用到的可能只有整个文件的 5% 不到,但是却要加载整个 css 文件,及其的影响页面渲染速度
这个时候就可以考虑对页面首屏的关键 CSS 进行内联,让页面渲染不被CSS 阻塞,再把完整 CSS 加载进来。
实现这个功能的插件叫 critters-webpack-plugin github地址为 https://github.com/GoogleChromeLabs/critters
安装:
npm i -D critters-webpack-plugin
使用:
// webpack.config.js
const Critters = require('critters-webpack-plugin');
module.exports = {
plugins: [
new Critters({
// 输出: <link rel="preload" onload="this.rel='stylesheet'">
preload: 'swap',
// 不要内联关键字体规则,而是预加载字体 URL:
preloadFonts: true
})
]
}
7、路由页面按需加载
引入页面 配置路由的时候,使用 ES6 的
import()动态按需导入文件 不仅仅是 页面文件,比如 某一个插件 只有那一个页面,某一个场景下,才能使用,那么就可以使用 import(‘xxxx/xxx/‘).then()
// 页面路由配置
const Demo = () => import ('@/pages/demo/demo.vue');
// 页面内按需加载文件
const btn = document.querySelector('.demand')
btn.onclick=()=>{
import('jszip').then(res=>{
// 业务代码
}
}
8、css相关的性能优化
8.1 页面渲染过程
**html**代码被**HTML**解析器解析成**DOM**树**css**代码被**css**解析引擎解析成**css**样式树- 将
**css**样式树和**DOM**树整合,生成渲染树 - 将根据渲染树样式,进行计算,生成最终的布局
- 浏览器引擎将计算后的布局,绘制出来,呈现在用户面前

8.2 页面渲染小常识
CSS是页面渲染的关键因素之一,(当页面存在外链**CSS**时,)浏览器会等待全部的**CSS**下载及解析完成后再渲染页面。关键路径上的任何延迟都会影响首屏时间,因而我们需要尽快地将**CSS**传输到用户的设备,否则,(在页面渲染之前,)用户只能看到一个空白的屏幕。- 网页生成的时候,至少会渲染一次。
- 在用户访问的过程中,还会不断重新渲染。
- 重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。
- 重排 比 重绘 影响大
8.2.1 重排(回流):
当
DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
引起重排的原因:
任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排,例如:
- 添加或者删除可见的
DOM元素; - 元素尺寸改变——边距、填充、边框、宽度和高度
- 内容变化,比如用户在
input框中输入文字 - 浏览器窗口尺寸改变——
resize事件发生时 - 计算
offsetWidth和offsetHeight属性 - 设置 style 属性的值
8.2.2 重绘:
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。
8.2.2.1 引起重绘的一些属性:
8.2.3 浏览器渲染机制
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。
div.style.width = "30px";
div.style.height = "30px";
div.style.left = "30px";
div.style.top = "30px";
//这只会渲染一次
强制刷新渲染队列:比如我们在渲染过程中,获取或者计算样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。
div.style.width = "30px";
console.log( div.style.width );
div.style.height = "30px";
console.log( div.style.height );
div.style.left = "30px";
console.log( div.style.left )
div.style.top = "30px";
console.log( div.style.top );
//渲染四次
8.2.4 影响渲染性能的点
- 重排
- 浏览器直到渲染树构建完成后才会渲染页面;
- 渲染树由 DOM 与 CSSOM 组合而成;
- DOM 是 HTML 加上(同步)阻塞的 JavaScript 操作(DOM 后的)结果;
- CSSOM 是 CSS 规则应用于 DOM 后的结果;
- 使 JavaScript 非阻塞非常简单,添加 async 或 defer 属性即可;
- 相对而言,要让 CSS 变为异步加载是比较困难的;
所以记住这条经验法则:(理想情况下,)最慢样式表的下载时间决定了页面渲染的时间。
8.3 基于上面的一些基础知识可以总结一下 css 优化性能的点
8.3.1 减少重排
8.3.1.1 分离读写操作
div.style.width = "30px";
div.style.height = "30px";
div.style.left = "30px";
div.style.top = "30px";
console.log( div.offsetLeft )
console.log( div.offsetheight );
console.log( div.offsetwidth );
console.log( div.offsettop );
8.3.1.2 样式集中改变
div.style.width = "30px";
div.style.height = "30px";
div.style.left = "30px";
div.style.top = "30px";
8.3.1.3 缓存布局信息
// bad
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
// good 缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
8.3.1.4 离线改变dom
- 隐藏要操作的dom
- 通过使用
**DocumentFragment**创建一个**dom**碎片,在它上面批量操作**dom**,操作完成之后,再添加到文档中,这样只会触发一次重排。 - 复制节点,在副本上工作,然后替换它
8.3.1.5 position 属性为 absolute 或 fixed
当该元素设置了定位之后,就会脱离文档流,当再改变该元素的样式的时候,就只是局部重排了。对大体上的影响很小
8.3.1.6 优化动画
启用
**GPPU**加速:Canvas2D,布局合成,CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video);
8.3.2 优先加载关键 CSS,懒加载其他 CSS;
找出首次渲染所需的样式(通常是首屏相关的样式),将它们内联到 标签中,其他样式则通过异步的方式进行加载。
8.3.3 根据媒体类型拆分代码
根据媒体查询拆分 CSS 文件,这样浏览器就会:
1. 以非常高的优先级下载符合当前上下文(设备、屏幕尺寸、分辨率、方向等)的 CSS 文件;
2. 阻塞关键路径;
3. 以非常低的优先级下载不符合当前上下文的 CSS 文件,不会阻塞关键路径。
<link rel="stylesheet" href="all.css" media="all" />
<link rel="stylesheet" href="small.css" media="(min-width: 20em)" />
<link rel="stylesheet" href="medium.css" media="(min-width: 64em)" />
<link rel="stylesheet" href="large.css" media="(min-width: 90em)" />
<link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" />
<link rel="stylesheet" href="print.css" media="print" />

浏览器仍然会下载全部的 CSS 文件,但只有符合当前上下文的 CSS 文件会阻塞渲染。
8.3.4 避免使用 @import
在 HTML 文档中应该避免使用 @import,在 CSS 文件中更应避免使用 @import,以及警惕预加载扫描器的怪异行为。
**@import** 渲染过程:
- 下载 HTML;
- 请求并下载依赖的 CSS;下载及解析完成后,本该是构造渲染树,然而;
- CSS 依赖了其他的 CSS,继续请求并下载 CSS 文件;
- 构造渲染树。
- 如果你没有包含
**@import**的**CSS**文件的修改权限,为了让浏览器并行下载**CSS**文件,可以往**HTML**中补充相应的**<link rel="stylesheet" src="@import的地址" />**。浏览器会并行下载相应的**CSS**文件且不会重复下载**@import**引用的文件。 - 在
**HTML**中使用**@import**,在以**WebKit**与**Blink**为内核的浏览器中,可能会触发它们预加载扫描器的 bug,在**Firefox**与**IE/Edge**中,则表现低效。
8.3.5 根据项目方案决定 CSS文件和 JavaScript 文件的加载顺序
- 在
CSS文件后的JavaScript仅在CSSOM构建完成后才会执行;如果你的JavaScript不依赖CSS;将它放置于CSS之前; - 如果
**JS**文件没有依赖**CSS**,你应该将**JS**代码放在样式表之前。 既然没有依赖,那就没有任何理由阻塞**JavaScript**代码的执行。
8.3.6 仅加载 DOM 依赖的 CSS:这将提高初次渲染的速度使让页面逐步渲染。
<html>
<head>
<link rel="stylesheet" href="core.css" />
</head>
<body>
<link rel="stylesheet" href="site-header.css" />
<header class="site-header">
<link rel="stylesheet" href="site-nav.css" />
<nav class="site-nav">...</nav>
</header>
<link rel="stylesheet" href="content.css" />
<main class="content">
<link rel="stylesheet" href="content-primary.css" />
<section class="content-primary">
<h1>...</h1>
<link rel="stylesheet" href="date-picker.css" />
<div class="date-picker">...</div>
</section>
<link rel="stylesheet" href="content-secondary.css" />
<aside class="content-secondary">
<link rel="stylesheet" href="ads.css" />
<div class="ads">...</div>
</aside>
</main>
<link rel="stylesheet" href="site-footer.css" />
<footer class="site-footer"></footer>
</body>
</html>
这样的结果是我们能逐步渲染页面,当前面的 CSS 可用时,页面将呈现对应的内容,(而不需等待全部 CSS 下载并解析完毕)
9、网络方面
9.1 网络请求方面。推荐使用 HTTP2 协议
9.1.1HTTP2协议相对 HTTP1 优点:
- 协议头压缩,更小的负载体积。
- 多路复用,支持 N 条请求并发请求。
请求优先级,更快的关键请求。(Web 性能优化:控制关键请求的优先级 翻译自:https://calibreapp.com/blog/critical-request,作者 Ben Schwarz,)
9.1.2
HTTP2协议相对HTTP1可能会废弃的优化方案资源合并。如
https://shanyue.tech/assets??index.js,interview.js,report.js。- 域名分片。
- 雪碧图(将无数小图片合并成单个大图片)。
控制面板查看协议方式:
- 打开控制面板,找到
network选项 - 以下任意右键,勾选
Protocol再请求,就可以看到当前的请求是走的 什么协议了!!!
9.2 充分利用 HTTP缓存
指定一定的缓存策略,对于 CDN来讲可减少回源次数,对于浏览器而言可减少请求发送次数。无论哪一点,对于二次网站访问都具有更好的访问体验。
9.2.1 缓存策略
9.2.1.1 强缓存
打包后带有 hash 值的资源 (如 /build/a3b4c8a8.js)
9.2.1.2 协商缓存
打包后不带 hash 值的资源 (如 /pages/index.html)
9.2.2 分包加载(bundle spliting)
避免一行代码修改导致整个 bundle 的缓存失效
9.3 请求资源体积务求更小
9.3.1 代码采用压缩混淆工具
- terser: terser ,如果需要集成
webpack的话可以使用 terser-webpack-plugin。对于terser的压缩速率,具体可以查看 https://try.terser.org/ - swc:swc,这个是使用
rust语言写的,具备更高的性能,拥有与terser相同的API - HTMLMinifier:HTML 代码压缩工具。具体可以查看 https://github.com/terser/html-minifier-terser
9.3.2 图片压缩
- 尽量使用
avif格式。前端发展的现在,webp普遍比jpeg/png更小,而avif又比webp小一个级别
9.3.3 自定义资源加载优先级(preload / prefetch)
preload加载当前路由必需资源,优先级高。一般对于Bundle Spliting资源与Code Spliting资源做preloadprefetch优先级低,在浏览器idle状态时加载资源。一般用以加载其它路由资源,如当页面出现Link,可prefetch当前Link的路由资源。(next.js默认会对link做懒加载+prefetch,即当某条Link出现页面中,即自动prefetch该Link指向的路由资源)<link rel="prefetch" href="style.css" as="style"> <link rel="preload" href="main.js" as="script">10、渲染优化
10.1 长列表优化
对于长列表的页面,由于页面需要渲染大量的
DOM这会导致页面变的特别卡顿。
对于以上的问题,于是乎出了两种解决方案:
- 只渲染可视区域
- 分片分批次渲染固定的数量的页面元素。
10.1.1 渲染可视区域
既然数据很多,但是用户每次只能看到部分的数据,那完全可以只渲染能看到的列表数据,对于看不见的列表,先不进行渲染,这就是 “虚拟列表优化”
插件介绍:
对于 react 项目,可以采用 [react-virtualized](https://github.com/bvaughn/react-virtualized)、[react-window](https://github.com/bvaughn/react-window)
对于 vue项目,可以使用 vue-virtual-scroll-list、vue-virtual-scroller
实现原理:
// VirtualList.vue
<template>
<!-- 展示区域 -->
<div class="wrap" ref="wrap" @scroll="handleScroll">
<!-- 为了显示滚动条 -->
<div ref="scrollHeight"></div>
<!-- 展示的内容 -->
<div class="visible-wrap" :style="{transform: `translateY(${offset}px)`}">
<div v-for="item in visibleData" :key="item.id" :id="item.id">
<slot :item="item"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
size: Number,
keeps: Number,
arrayData: Array
},
data() {
return {
start: 0,
end: this.keeps,
offset: 0 // 列表内容的偏移量
}
},
computed: {
visibleData() {
return this.arrayData.slice(this.start, this.end)
}
},
mounted() {
this.$refs.scrollHeight.style.height = this.arrayData.length * this.size + 'px'
this.$refs.wrap.style.height = this.keeps * this.size + 'px'
},
methods: {
handleScroll() {
const scrollTop = this.$refs.wrap.scrollTop
// 计算从下标为几的一项开始渲染,减 1 是因为渲染的数据是从第 0 项开始的
this.start = Math.ceil(scrollTop / this.size) - 1 >= 0 ? Math.ceil(scrollTop / this.size) - 1 : 0
this.end = this.start + this.keeps
// 当列表向上(下)滚动时,为了让渲染的列表一直处于可视范围内,就要把列表向下(上)挪
this.offset = this.start * this.size
}
}
}
</script>
<style scoped lang="less">
.wrap {
position: relative;
overflow-y: scroll;
}
.visible-wrap {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
</style>
- 每一项
**Item**高度固定- 列表的数据来源是一个长度为 1000 的简单的 list 数组
- 我们的目标是只需要传递 3 个值给 VirtualList 组件,就可以正常使用:
size:每一项的高度keeps:希望展示几条数据arrayData:列表数据
VirtualList组件里得有 3 个部分:- 最外层容器区域。高度固定,超出区域出现滚动条,高度为传入的
size乘上keeps; - 列表本应该有的高度区域,也就是列表如果全部渲染的总高度。因为只渲染
keeps指定的条数的数据,就会导致没有滚动条或滚动条无法起到预告总的列表长度的功能,所以要用一个高度为列表总长度的div让滚动条正确显示; - 要展示的内容。展示的数据应该是总数据
arrayData的某一部分。展示的数据item还得传给父组件,在父组件进行使用,这里就用到了插槽。
- 最外层容器区域。高度固定,超出区域出现滚动条,高度为传入的
- 当滚动列表时(
handleScroll触发),我们要及时的根据滚动的距离更新应该显示的数据:onscroll处理的是对象内部内容区的滚动事件,所以是对最外部固定高度的wrap容器进行监听。- 如下图所示:蓝色矩形为可视区域,假设传入的
keeps为 3 ,当滚动列表(红色矩形)时,渲染的列表区域,也就是 3 个item(深蓝绿色矩形) 占据的区域也会跟着滚动,如果仅仅改变渲染的内容,也就是根据滚动距离从item1开始渲染,那么此时这个item1就会替换下图的item0,位于可视区域之外,无法被看见。

所以需要根据已经滚动出可视区域的 item 的个数和每一项 item 的高度的乘积(offset)进行反向的移动,移动的距离为 this.start * this.size。注意: offset 的值在多数情况下不会等于 scrollTop 的值。
注:一个元素的
scrollTop值是这个元素的内容顶部到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的scrollTop值为 0。
解决滚动时,如果刚好渲染的第一项只显示了部分,那么可视区域的最底下就会出现相应高度的空白的问题,就如下图中,可以看到右侧滚动条的高度超过 23 那一项的区域为空白:
解决方案:在原先的渲染项数基础上,再多向前和后渲染若干项,那么决定渲染哪些数据的 visibleData 的计算就会发生变化,原先是定义了 start 和 end 用于标记到底切割哪一部分,代码如下:
visibleData() {
return this.arrayData.slice(this.start, this.end)
}
现在则是新定义了 prevCount、renderStart、nextCount 和 renderEnd 4 个参数,另外 offset 也需要改变为 (this.start - this.prevCount) * this.size,因为假设原本渲染 8 项,往上滚动了 1 项的距离,那么渲染的 8 项由 0 ~ 7 变为 1 ~ 8,0 项被删除,这时需要把 class="visible-wrap" 的这个 div 往下移动 1 项,才能刚好在可视区域的顶部看到第 1 项;现在则是往上移动 1 项时,class="visible-wrap" 这个 div 里渲染的项数就会变为 1+8+8=17 项,0 项不会被删除,不需要再往下移动 1 项,所以会有 this.start - this.prevCount
- 每一项
**Item**高度不固定 ```vue // VirtualList.vue
原来对于列表如果全部渲染应该有的高度的计算 `this.arrayData.length * this.size + 'px'` 显然不合适了,因为每项 `item` 的高度 `size` 不确定了。在开始重新计算之前,先介绍一下二分法:
- **二分法:**
- 使用前提:数组已经按升序排列
- 基本原理:首先将要查找的值(`value`)同数组中间那一项的值(`midValue`)进行比较,当 `start <= end`
1. 如果` value < midValue`,则 `end = midValue - 1`,只需要在数组的前一半元素中继续查找
2. 如果` value = midValue`,匹配成功,查找结束
3. 如果 `value > midValue`,则 `start = midValue + 1`,只需要在数组的后一半元素中继续查
4. 如果 `while` 循环结束后都没有找到 `value`,返回 -1
```javascript
const arr = [-1, 5, 6, 12, ...]
const start = 0,
end = arr.length -1,
midValue = start + (end - start) / 2
注意: 在二分法中,计算中间项的索引时用的是 midValue = start + (end - start) / 2 而不是直接使用更简单的公式 midValue = (start + end) / 2,是为了防止值溢出的情况,因为 start + end 的值可能会大于 js 最大的能表示的数。(如果 start < 0 或 end < 0时,end - start 也可能会溢出)
利用二分法重新计算 **start**:
- 在页面加载完毕后,对数据数组里每一项的
height, top和bottom的值做个缓存(此时的size为我们预估的,滚动条的高度并不准确),存放在数组positionListArr里; - 用二分法开始查找,我们页面滚动的距离
scrollTop对应于positionListArr里的哪一项的bottom的值。之所以用二分法是因为后面会根据真实dom重行计算每一项的height, top和bottom,到时候每一项的size就可能不一样了; - 之后对于
end和offset计算原理就跟item高度固定的情况一样了。
页面更新后:
页面渲染完成后,获取到真实的 dom,更正缓存在 positionListArr 里的数据,实现更新滚动条的高度。这部分代码用到了 ref 和 getBoundingClientRect 的相关知识:
- 如果
ref是写在v-for的元素或组件的时候,引用信息将是包含DOM节点或组件实例的数组。 Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置,除了width和height以外的属性是相对于视图窗口的左上角来计算的,如下图:

代码源码:https://github.com/chaimHL/vue-long-list-optimization
10.1.2 分片渲染
因为 requestAnimationFrame 或定时器是一个宏任务,所以每执行一次 GUI 渲染后就执行一次相关的回调,也就实现了每次添加 50 个 li 节点,从而达到了分片加载的目的。现在加载时间则如下:
const time = Date.now()
/**
* index: 记录循环到哪了
* id: 往 li 里添加的内容
*/
let index = 0, id = 0
function load() {
index += 50
if (index < 10000) {
requestAnimationFrame(() => { // 用 requestAnimationFrame(也是宏任务)代替了 setTimeout,性能更好点
const fragment = document.createDocumentFragment() // IE 浏览器需要使用文档碎片,一般可不用
for (let i = 0; i < 50; i++) {
const li = document.createElement('li')
li.innerText = id++
fragment.appendChild(li)
}
list.appendChild(fragment)
})
load()
}
}
load()
console.log(Date.now() - time)
setTimeout(() => {
console.log(Date.now() - time)
})
与分片加载之前对比,确实快了许多。但这种方案有个问题:会导致页面的 dom 元素过多,依旧容易造成卡顿。
11、线程优化
11.1 webWorker
有时候一些实时代码编译及转换 如果纯碎使用传统的
Javascript实现,将会耗时过多阻塞主线程,有可能导致页面卡顿。
如果使用 Web Worker 交由额外的线程来做这件事,将会高效很多,基本上所有在浏览器端进行代码编译的功能都由 Web Worker 实现。MDN 关于 webWorker 的介绍

