迈向中高级前端工程师要必备14种性能优化方案

这里写目录标题

  • 1、启用前端缓存
    • 1、什么是web缓存(前端缓存)
    • 2、缓存可以解决什么问题?他的缺点是什么?
    • 3、强制缓存原理讲解
      • 3.1.基于Expires字段实现的强缓存(已经废弃,了解即可)
      • 3.2.基于Cache-control实现的强缓存(代替Expires的强缓存实现方法)
    • 4、协商缓存原理讲解
      • 4.1.基于last-modified实现的协商缓存
      • 4.2.基于ETag实现的协商缓存
    • 补充
      • 如何设置缓存
      • 哪些文件对应哪些缓存
    • 总结
  • 2、开启GZIP压缩
    • react
    • vue
  • 3、函数节流和防抖
  • 4、异步加载script文件或将script文件放在最后加载
  • 5、减少重排和重绘
  • 6、使用服务器渲染
  • 7、将png/jpg/gif图片替换为webp格式图片
  • 8、合并请求
  • 9、启用事件委托(事件代理)
  • 10、尽量使用CSS完成动画效果
  • 11、适当使用memo --- React篇
  • 12、使用懒加载
  • 13、使用骨架屏
  • 14、将moment.js换成day.js

1、启用前端缓存

所谓的前端缓存,其实就是http缓存,通过(强缓存/协商缓存)等方式让计算机直接从缓存中读取静态资源,从而实现节约宽带,提高响应速度,减少服务器压力等优化。

1、什么是web缓存(前端缓存)

web缓存主要指的是两部分:浏览器缓存和http缓存。
浏览器缓存
比如,localStorage,sessionStorage,cookie等等。这些功能主要用于缓存一些必要的数据,比如用户信息。比如需要携带到后端的参数。亦或者是一些列表数据等等。像localStorage,sessionStorage这种用户缓存数据的功能,他只能保存5M左右的数据,多了不行。cookie则更少,大概只能有4kb的数据

http缓存
官方介绍:Web 缓存是可以自动保存常见文档副本的 HTTP 设备。当 Web 请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这 个文档。
但是,服务器需要处理http的请求,并且http去传输数据,需要带宽,带宽是要钱买的啊。而我们缓存,就是为了让服务器不去处理这个请求,客户端也可以拿到数据。
注:缓存主要是针对html,css,img等静态资源,常规情况下,我们不会去缓存一些动态资源,因为缓存动态资源的话,数据的实时性就不会不太好,所以我们一般都只会去缓存一些不太容易被改变的静态资源。

2、缓存可以解决什么问题?他的缺点是什么?

缓存可以解决的问题:
减少不必要的网络传输,节约宽带(省钱)
更快的加载页面(加速)
减少服务器负载,避免服务器过载的情况出现。(减载)
缺点:
占内存(有些缓存会被存到内存中)

开发中最关心的,还是"更快的加载页面";尤其是对于react/vue等SPA(单页面)应用来说,首屏加载是老生常谈的问题。这个时候,缓存就显得非常重要。不需要往后端请求,直接在缓存中读取。速度上,会有显著的提升。是一种提升网站性能与用户体验的有效策略。

http缓存又分为两种两种缓存,强制缓存和协商缓存,深度剖析一下强制缓存和协商缓存各自的优劣以及他们的使用场景以及使用原理

3、强制缓存原理讲解

强制缓存,我们简称强缓存。
从强制缓存的角度触发,如果浏览器判断请求的目标资源有效命中强缓存,如果命中,则可以直接从内存中读取目标资源,无需与服务器做任何通讯。

3.1.基于Expires字段实现的强缓存(已经废弃,了解即可)

在以前,通常会使用响应头的Expires字段去实现强缓存
Expires字段的作用是,设定一个强缓存时间。在此时间范围内,则从内存(或磁盘)中读取缓存返回。
比如说将某一资源设置响应头为:Expires:new Date(“2022-7-30 23:59:59”);
那么,该资源在2022-7-30 23:59:59 之前,都会去本地的磁盘(或内存)中读取,不会去服务器请求。
但是,Expires已经被废弃了。对于强缓存来说,Expires已经不是实现强缓存的首选。

废弃原因
因为Expires判断强缓存是否过期的机制是:获取本地时间戳,并对先前拿到的资源文件中的Expires字段的时间做比较。来判断是否需要对服务器发起请求。这里有一个巨大的漏洞:“如果我本地时间不准咋办?”
Expires过度依赖本地时间,如果本地与服务器时间不同步,就会出现资源无法被缓存或者资源永远被缓存的情况。所以,Expires字段几乎不被使用了。现在的项目中,我们并不推荐使用Expires,强缓存功能通常使用cache-control字段来代替Expires字段。

3.2.基于Cache-control实现的强缓存(代替Expires的强缓存实现方法)

Cache-control这个字段在http1.1中被增加,Cache-control完美解决了Expires本地时间和服务器时间不同步的问题。是当下的项目中实现强缓存的最常规方法。
Cache-control的使用方法页很简单,只要在资源的响应头上写上需要缓存多久就好了,单位是秒。

//往响应头中写入需要缓存的时间
res.writeHead(200,{
    'Cache-Control':'max-age=10'
});

上述代码的设置:
从该资源第一次返回的时候开始,往后的10秒钟内如果该资源被再次请求,则从缓存中读取

Cache-Control:max-age=N,N就是需要缓存的秒数。从第一次请求资源的时候开始,往后N秒内,资源若再次请求,则直接从磁盘(或内存中读取),不与服务器做任何交互。

Cache-control中因为max-age后面的值是一个滑动时间,从服务器第一次返回该资源时开始倒计时。所以也就不需要比对客户端和服务端的时间,解决了Expires所存在的巨大漏洞。
Cache-control有max-age、s-maxage、no-cache、no-store、private、public这六个属性:
1、max-age决定客户端资源被缓存多久。
2、s-maxage决定代理服务器缓存的时长。
3、no-cache表示是强制进行协商缓存。
4、no-store是表示禁止任何缓存策略。
5、public表示资源即可以被浏览器缓存也可以被代理服务器缓存。
6、private表示资源只能被浏览器缓存。

no-cache和no-store
no_cache是Cache-control的一个属性。它并不像字面意思一样禁止缓存,实际上,no-cache的意思是强制进行协商缓存。如果某一资源的Cache-control中设置了no-cache,那么该资源会直接跳过强缓存的校验,直接去服务器进行协商缓存。而no-store就是禁止所有的缓存策略了。
注意,no-cache和no-store是一组互斥属性,这两个属性不能同时出现在Cache-Control中。

public和private
一般请求是从客户端直接发送到服务端,服务端响应请求

但有些情况下是例外的:比如,出现代理服务器:
客户端发送请求到代理服务器,代理服务器转发请求到服务端,服务端响应代理服务器请求,代理服务器再响应给客户端

而public和private就是决定资源是否可以在代理服务器进行缓存的属性。
其中,public表示资源在客户端和代理服务器都可以被缓存。
private则表示资源只能在客户端被缓存,拒绝资源在代理服务器缓存。
如果这两个属性值都没有被设置,则默认为private

注意,public和private也是一组互斥属性。他们两个不能同时出现在响应头的cache-control字段中。

max-age和s-maxage
max-age表示的时间资源在客户端缓存的时长,而s-maxage表示的是资源在代理服务器可以缓存的时长。
在一般的项目架构中max-age就够用。
而s-maxage因为是代理服务端的缓存时长,他必须和上面说的public属性一起使用(public属性表示资源可以在代理服务器中缓存)。

注意,max-age和s-maxage并不互斥。他们可以一起使用。
那么,Cache-control如何设置多个值呢?用逗号分割,如下↓
Cache-control:max-age=10000,s-maxage=200000,public

强制缓存就是以上这两种方法了。现在我们回过头来聊聊,Expires难道就一点用都没有了吗?也不是,虽然Cache-control是Expires的完全替代品,但是如果要考虑向下兼容的话,在Cache-control不支持的时候,还是要使用Expires,这也是我们当前使用的这个属性的唯一理由。

4、协商缓存原理讲解

4.1.基于last-modified实现的协商缓存

1、首先需要在服务器端读出文件修改时间,
2、将读出来的修改时间赋给响应头的last-modified字段。
3、最后设置Cache-control:no-cache

.....
const {mtime} = fs.readFileSync('图片资源') //读取修改时间
res.setHeader('last-modified',mtime.toUTCString()) //设置文件最后修改时间
res.setHeader('Cache-Control','no=cache') 
.....

第一行,读出修改时间。
第二行,给该资源响应头的last-modified字段赋值修改时间
第三行,给该资源响应头的Cache-Control字段值设置为:no-cache.(上文有介绍,Cache-control:no-cache的意思是跳过强缓存校验,直接进行协商缓存。)

到这还未实现协商缓存
当客户端读取到last-modified的时候,会在下次的请求标头中携带一个字段:If-Modified-Since。
这个请求头中的If-Modified-Since就是服务器第一次修改时候给他的时间,

.....
res.setHeader('last-modified',mtime.toUTCString()) //设置文件最后修改时间
.....

那么之后每次对该资源的请求,都会带上If-Modified-Since这个字段,而服务端就需要拿到这个时间并再次读取该资源的修改时间,让他们两个做一个比对来决定是读取缓存还是返回新的资源。

.....
//协商缓存
const data = fs.readFileSync('图片资源') //读取资源
const {mtime} = fs.readFileSync('图片资源') //读取修改时间
//读取第一次返回给客户端的文件修改时间
const ifModifiedSince = read.header['if-modified-since']
//比较第一次的修改时间和资源文件当前的修改时间是否一致
if(ifModifiedSince  === mtime.toUTCString()){
	//如果一致,说明文件没有被修改过,则返回304
	res.statusCode = 304
	//因为缓存已经生效,这里不需要返回资源data
	res.end()
	//缓存生效后直接return结束工作,避免返回新的last-modified
	return
}
res.setHeader('last-modified',mtime.toUTCString()) //设置文件最后修改时间
res.setHeader('Cache-Control','no=cache')  //设置协商缓存策略
res.end(data)
.....

完毕。

协商缓存流程图,相对纯文字来说,清晰明了点
迈向中高级前端工程师要必备14种性能优化方案_第1张图片
使用以上方式的协商缓存已经存在两个非常明显的漏洞。这两个漏洞都是基于文件是通过比较修改时间来判断是否更改而产生的。
1.因为是更具文件修改时间来判断的,所以,在文件内容本身不修改的情况下,依然有可能更新文件修改时间(比如修改文件名再改回来),这样,就有可能文件内容明明没有修改,但是缓存依然失效了。
2.当文件在极短时间内完成修改的时候(比如几百毫秒)。因为文件修改时间记录的最小单位是秒,所以,如果文件在几百毫秒内完成修改的话,文件修改时间不会改变,这样,即使文件内容修改了,依然不会
返回新的文件。
为了解决上述的这两个问题。从http1.1开始新增了一个头信息,ETag(Entity 实体标签)

4.2.基于ETag实现的协商缓存

ETag就是将原先协商缓存的比较时间戳的形式修改成了比较文件指纹。
文件指纹:根据文件内容计算出的唯一哈希值。文件内容一旦改变则指纹改变。

流程:
1.第一次请求某资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的etag字段中跟资源一起返回给客户端。
2.第二次请求某资源的时候,客户端自动从缓存中读取出上一次服务端返回的ETag也就是文件指纹。并赋给请求头的if-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端。
3.服务端拿到请求头中的is-None-Match字段值(也就是上一次的文件指纹),并再次读取目标资源并生成文件指纹,两个指纹做对比。如果两个文件指纹完全吻合,说明文件没有被改变,则直接返回304状态码和一个空的响应体并return。如果两个文件指纹不吻合,则说明文件被更改,那么将新的文件指纹重新存储到响应头的ETag中并返回给客户端

代码和校验流程看上述的代码块和流程图,对比着理解

从校验流程上来说,协商缓存的修改时间比对和文件指纹比对,几乎是一样的。

ETag缺点:
ETag需要计算文件指纹这样意味着,服务端需要更多的计算开销。。如果文件尺寸大,数量多,并且计算频繁,那么ETag的计算就会影响服务器的性能。显然,ETag在这样的场景下就不是很适合。

ETag有强验证和弱验证,所谓将强验证,ETag生成的哈希码深入到每个字节。哪怕文件中只有一个字节改变了,也会生成不同的哈希值,它可以保证文件内容绝对的不变。但是,强验证非常消耗计算量。ETag还有一个弱验证,弱验证是提取文件的部分属性来生成哈希值。因为不必精确到每个字节,所以他的整体速度会比强验证快,但是准确率不高。会降低协商缓存的有效性。

值得注意的一点是,能用cache-control就不要用expiress。ETag并不是last-modified的完全替代方案。而是last-modified的补充方案,项目中到底是用ETag还是last-modified完全取决于业务场景,这两个没有谁更好谁更坏。

补充

如何设置缓存

从前端的角度来说:
你什么都不用干,缓存是缓存在前端,但实际上代码是后端的同学来写的。如果你需要实现前端缓存的话啊,通知后端的同学加响应头就好了。
从后端的角度来说
参考文章,文章里的后端是使用node.js写的。对于后端的同学来说。应该不难看懂。

哪些文件对应哪些缓存

有哈希值的文件设置强缓存即可。没有哈希值的文件(比如index.html)设置协商缓存

总结

1、http缓存可以减少宽带流量,加快响应速度。
2、关于强缓存,cache-control是Expires的完全替代方案,在可以使用cache-control的情况下不要使用expires
3、关于协商缓存,etag并不是last-modified的完全替代方案,而是补充方案,具体用哪一个,取决于业务场景。
4、有些缓存是从磁盘读取,有些缓存是从内存读取,有什么区别?答:从内存读取的缓存更快。
5、所有带304的资源都是协商缓存,所有标注(从内存中读取/从磁盘中读取)的资源都是强缓存

2、开启GZIP压缩

这主要针对工程化项目,如react/vue等。
常规情况下前端部署所需要的dist包中会有一些静态文件(如js,css,图片文件)。这些静态文件会在项目初始化后续某个动作下被加载。出于体积大小的不同,加载速度也不一样。有些文件比较大,加载所需时间相对较长,针对文件加载慢的情况。
我们可以采用一些压缩方案,让这些静态文件的体积尽量变小。这样,就可以相对的节约宽带,而因为这些文件的变小,对这些的静态文件的加载的速度也会得到提升,客户端也可以尽快响应给用户一个良好的体验。
gzip有着比zip更优秀的压缩算法,可以有效的减少文件的大小。
每个框架配置gzip的方法都不太一样,没有标准答案。但是大概的流程都是一样的。

1、下载compression-webpack-plugin插件
2、配置到webpack中
3、通知后端开启gzip

完成
以下是两种框架的开启案例

react

第一步:安装 compression-webpack-plugin 插件

npm install compression-webpack-plugin

第二步:在config.ts或.umirc.ts中进行配置

chainWebpack: function (config, { webpack }) {
    config.merge({
      optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 1000,
          minChunks: 2,
          automaticNameDelimiter: '.',
          cacheGroups: {
            vendor: {
              name: 'vendors',
              test({ resource }) {
                return /[\\/]node_modules[\\/]/.test(resource)
              },
              priority: 10,
            },
          },
        },
      },
    })
    //在生产环境开启gzip压缩
    if (isProd) {
      // Gzip压缩
      config.plugin('compression-webpack-plugin').use(CompressionPlugin, [
        {
          test: /\.(js|css|html)$/i, // 匹配
          threshold: 10240, // 超过10k的文件压缩
          deleteOriginalAssets: false, // 不删除源文件
        },
      ])
    }
  }

配置完成后使用npm run build 打包

查看dist文件夹中出现了.gz结尾的压缩包,就是压缩成功了

到这里只是前端完成了gzip压缩。如果在项目中需要gzip压缩之后的压缩文件。还需要后端在nginx中配置 gzip_static on

//nginx 配置
gzip_static  on  //检测是否存在gzip文件,有,则返回给客户端

最后,运行项目,找一个css或者js或者html后缀的文件,查看响应头中的 Content-Encoding 字段。显示为gzip则表示gzip在项目中使用成功。

vue

第一步、安装compression-webpack-plugin插件,此插件就是用于打包压缩的。

// 这一步如果失败是插件版本过高,建议安装1.1.12版本
// npm install --save-dev compression-webpack-plugin
npm install --save-dev compression-webpack-plugin@1.1.12

第二步、配置webpack文件,开启gzip压缩功能(文件位于config/index.js)。

	// Gzip off by default as many popular static hosts such as
	// Surge or Netlify already gzip all static assets for you.
	// Before setting to `true`, make sure to:
	// npm install --save-dev compression-webpack-plugin 安装插件依赖
    productionGzip: true,
    productionGzipExtensions: ["js", "css"],

第三步、修改配置

// build/webpack.prod.conf.js
if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      // asset: '[path].gz[query]',
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

asset改成filename后,压缩插件版本号要对应1.x
第四步、打包

npm run build

Nginx开启gzip功能配置
找到conf目录下的nginx.conf ,开启gzip并设置gzip_types的类型

  #打开gzip压缩
  gzip on;
  #不压缩临界值,大于1K的才压缩,一般不用改
  gzip_min_length 1k;
  #设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流,这里设置以16k为单位的4倍申请内存
  gzip_buffers 4 16k;
  #默认为http 1.1,现在99.99%的浏览器基本上都支持gzip解压了,所有无需设置此项
  #gzip_http_version 1.0;
  #gzip压缩比,1 最小处理速度最快,9 最大但处理最慢(传输快但比较消耗cpu)
  gzip_comp_level 3;
  #要压缩的文件类型,注意"text/html"类型无论是否指定总是会被压缩的
  gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/x-icon;
  #on的话会在Header里增加"Vary: Accept-Encoding",给代理服务器用的,有的浏览器支持压缩,有的不支持,所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩
  #我这里的浏览器肯定支持gzip压缩,所以就不开启此功能了
  gzip_vary off;
  #IE6对Gzip不怎么友好,不给它Gzip压缩了
  gzip_disable "MSIE [1-6]\.";

重新部署服务器

nginx -s reload

查看是否成功
打开控制台,可以看到Network下的Response Headers中返回了Content-Encoding: gzip,表明gzip开启成功。
而Request Headers里面的Accept-Encoding: gzip只是表示前端(用户浏览器)支持gzip的压缩方式。

服务器支持gzip的方式可以有两种:
1、打包的时候生成对应的.gz文件,浏览器请求xx.js时,服务器返回对应的xxx.js.gz文件
2、浏览器请求xx.js时,服务器对xx.js进行gzip压缩后传输给浏览器

3、函数节流和防抖

节流和防抖是经典的优化方案,比较简单,理解优化的意义在哪就行。
节流和防抖都是将我们大量重复多余的操作进行合并,以达到减少客户端或服务端压力,提高运算速度,减少http请求等效果。

不论是函数防抖还是函数节流,都是性能优化的一种手段,都是为阻止函数无意义的执行,减小dom压力,避免不必要的性能浪费。
下面都是封装好的

节流函数

  //节流函数
  /*
      @params (入参)
      callback:需要节流的函数。   必传!
      time:节流间隔时间点(也就是多久触发一次)不传的话默认是 300 毫秒
  */
 //封装好的节流函数
  const onScroll = (callback,time = 300) => {
    let state = true; //触发判断条件
    //判断如否有函数传入
    if(typeof callback !== 'function'){
      throw '第一个入参必须是函数,需要被节流的函数'
    }
    //制作一个闭包环境
    return () => {
      if(state){
        callback();
        state = false;
        setTimeout(() => {
          state = true;
        },time)
      }
    }
  }

防抖函数


  //防抖函数
  /*
      @params (入参)
      callback:需要节流的函数。   必传!
      time:防抖间隔时间点(也就是倒计时触发的缓冲时间)不传的话默认是 300 毫秒
  */
  const onchange = (callback, time = 300) => {
    let asyncFun;
    //判断需要被防抖的函数是否传入
    if(typeof callback !== 'function'){
      throw '第一个入参必须是函数,需要进行防抖的函数'
    }
    //创建一个闭包环境
    return () => {
    //在上一个函数被触发前,销毁他
      if (asyncFun !== undefined) clearTimeout(asyncFun);
      //创建一个新的函数
      asyncFun = setTimeout(() => {
        callback();
      }, time)
    }
  }

4、异步加载script文件或将script文件放在最后加载

浏览器在下载和解析script文件的时候会停止html的解析和 CSSOM 的构建。
所以,在以前我们通常喜欢把< script >标签放在html的最后面。
当然,不想将< script >标签放在后面又不想让script的下载和解析影响html的渲染,也有方案。在script标签中加上defer属性即可。
script标签的defer属性可以让script异步加载并在DOM构建完成和CSS渲染完毕之后再执行。

5、减少重排和重绘

重排和重绘是浏览器中相对比较耗时的动作。尤其是重排。
重绘不一定会引起重排。重排一定会导致重绘。
浏览器上我们所能看见的元素。当它们的位置发生改变的时候,并不是流动的。而是先被擦除,再重新生成。这就像画画,当画上的某一个单位需要改变位置,我们无法直接把这个单位直接进行移动,只能先将其擦除,然后在指定的位置重新画一个。
元素改变位置,浏览器会先在指定位置上构建该元素的dom(重排)(注意这里没有渲染),然后在对该元素进行渲染(比如background,color)(重绘)。
元素在位置上的改变属于重排,非位置上的改变基本属于重绘(不绝对)。
比如一个原本红色背景的div,如果仅仅改变背景为蓝色的话,那么只会触发重绘,并不会触发重排。(因为位置没有变,只有CSS改变)
重绘触发场景
1、background的改变
2、color的改变
3、visibility:hidden
4、css3的translate
5、color, border-style, border-radius, visibility, text-decoration, background, background-image, background-position, background-repeat, background-size,outline-color, outline-style, outline-width, box-shadow
重排的触发场景
1、删除或者新增一个节点元素
2、元素位置的改变,比如float,position,overflow,display等等
3、元素尺寸的改变,比如margin,padding,height,width等等
4、初始化构建DOM树的时候
5、窗口尺寸的变化 也就是resize事件发生的时候
6、填充内容的改变(内容撑大了某一个节点,内容改变,包含它的节点大小自然跟随调整。)
7、读取某一个元素的时候,比如offsetLeft,offsetTop,offsetHeight,offsetWidth, clientTop,clientLeft,clientWidth,clientHeight, scrollTop,scrollLeft,scrollWidth,scrollHeight, width,height等等

6、使用服务器渲染

如果使用服务端渲染(SSR)的话,首先首屏加载速度会有显著的提升(因为SSE只需要加载首页一个页面)。并且对SEO也很友好。

当然它也有弊端:页面数据更容易被爬。服务器压力会变大。

nuxt.js和next.js等都是比较流行SSR框架。

7、将png/jpg/gif图片替换为webp格式图片

webp格式的图片比png/jpg有着更优秀的算法。在图片体积上会比jpg/png更小。所以加载的也就更快,耗费的带宽也就越少。占用加载资源的时间也就越短。

webp格式提供有损压缩和无损压缩两种方案。

8、合并请求

为什么要合并请求?
为了减少请求时间,为了减小服务器压力。ajax请求并不是没有成本的。每次请求都需要进行TCP的三次握手和四次挥手,解析报文等一系列的过程,这些过程都需要时间去执行。并且,浏览器在同一域名下的请求并发数有限制,同一域名下同一个请求只能并发一个,不同类型请求(比如GET/POST)并发个数基本在4-6个之间。假设当前浏览器的并发请求有6个。那么第7个请求就需要等前6个请求中任意一个完成以后才可以从任务队列中被拉出去执行。所以,合并请求可以在一定程度上减少资源响应时间,给用户带来更好的使用体验。
合并请求的基本方案
1、使用精灵图(合并静态图片资源请求)
2、合理合并get请求,在适当的情况下,我们可以将一些可以合并的get请求合并为一个

9、启用事件委托(事件代理)

事件代理 === 事件委托 (一个东西,叫法不同,以下简称事件委托)

什么是事件委托?利用事件冒泡机制将原本应该绑定在子元素上的事件全部交由父元素来完成的行为被称为事件委托。

事件委托可以减少内存消耗和DOM操作
事件委托适用场景:列表数据和瀑布流数据等需要大量绑定相同功能的函数的场景。

10、尽量使用CSS完成动画效果

一些简单的,需要手动绘制的动画,在CSS可以完成的情况下,尽量避免使用JS完成动画。
使用CSS完成动画的好处是:

1、不占用主线程(js是需要占用的)
2、可以利用硬件加速
3、在不可见时动画不会持续执行

当然,如果项目本身存在动画库,建议使用动画库。如果动画复杂,无法使用css完成(比如需要绑定函数),那么建议用JS完成动画。

11、适当使用memo — React篇

react的渲染机制,只要父组件的state改变,那么不论子组件是否发生改变。子组件都会重新渲染。这就导致了一个问题,如果父组件包含N个子组件并且父组件渲染频繁的情况下。N个子组件会一直重新渲染。
这个问题可以由React中的memo解决。memo是一个缓存组件。使用memo包含子组件,那么在memo的依赖不改变的情况下。子组件不会随着父组件的渲染而重新渲染。当然,这不意味着我们可以滥用memo,memo本身是存在成本的。没有必要给每一个组件都加上memo。比如父组件如果不存在重新渲染的情况,那么子组件就没有必要用memo包含。

memo有两个坑点请注意:
1、如果传入函数,在函数没有被useCallback包涵的情况下,父组件渲染后,每次默认传入的函数都是一个新函数,意味着memo包含的子组件每次都会重新渲染。
2.memo校验的是props中传入变量的栈内存地址,像传入对象这种复杂数据类型,数据是存储在堆内存的情况下,不new一个新对象,老对象里面的数据改变了memo也监测不到。无法及时更新。

12、使用懒加载

对于一些不必要立即显示的节点,我们可以采用懒加载技术。在需要使用到的时候,再去加载该文件(组件),以减少不必要的内存占用和页面负载。

13、使用骨架屏

对于用户来说,很多用户会在网页端长时间的白屏状态下失去耐心,然后离开页面。所以在数据查询速度慢,或者资源体积大,数量多无法第一时间返回等浏览器无法快速接受并将数据渲染到视图上的情况下,除了可以采用代码压缩,启动缓存等方案外,我们还可以采用骨架屏的方式来给客户挽回一点体验。

14、将moment.js换成day.js

好处:
1、day.js的体积比moment.js小。moment.js有70多kb,但是day.js只有2kb。像微信小程序这种对代码包大小有要求的情况下,day.js会是比moment.js更好的选择。很多官方的框架和库都已经将moment.js换成了day.js。
2、moment已经好几年没更新了。但是day.js仍在持续更新中。

从moment.js迁移到day.js学习成本并不高,因为day.js是moment.js的微缩版,api相似度极高。
day.js官网

你可能感兴趣的:(前端,性能优化)