js性能优化(五)

news2024/11/24 18:46:41

第五章开始啦~~~~~~~~~~~~~

防抖和节流之前自己有学过一次,包括几种方式怎么实现,代码如何写花了两天有写过,这次算是更系统的一个复习加填补

十七、防抖与节流

为什么需要防抖和节流:

在一些高频率事件触发的场景下我们不希望对应的事件处理函数多次执行

场景:

滚动事件(滚轮)
输入的模糊匹配
轮播图切换
点击操作

为什么会出现这样的情况?

浏览器默认情况下都会有自己的监听事件间隔( 4~6ms),如果检测到多次事件的监听执行,那么就会造成不必要的资源浪费,所以这里需要一种机制,就是防抖和节流

前置的场景: 界面上有一个按钮,我们可以连续多次点击

防抖:对于这个高频的操作来说,我们只希望识别一次点击,可以人为是第一次或者是最后一次

节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,按着我们定义的频率减少触发的次数

1.防抖实现

/** 
 * handle 最终需要执行的事件监听
 * wait 事件触发之后多久开始执行,不写形参是为了方法的形参等问题的处理较为麻烦
 * immediate 控制执行第一次还是最后一次,false 执行最后一次
*/
function myDebounce(handle, wait, immediate) {

  // 参数类型判断及默认值处理
  if (typeof handle !== 'function') throw new Error('handle must be an function')
  if (typeof wait === 'undefined') wait = 300
  if (typeof wait === 'boolean') {
    immediate = wait
    wait = 300
  }
  if (typeof immediate !== 'boolean') immediate = false

  // 所谓的防抖效果我们想要实现的就是有一个 ”人“ 可以管理 handle 的执行次数
  // 如果我们想要执行最后一次,那就意味着无论我们当前点击了多少次,前面的N-1次都无用
  // return function的时候直接初始化的返回了一个函数赋值给了按钮的点击事件,然后点击按钮的时候
  //this和...args是这两个参数才会赋值给这个函数,所以不用箭头函数可以获取到this和args
  let timer = null
  return function proxy(...args) {
    let self = this,init = immediate && !timer
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      !immediate ? handle.call(self, ...args) : null
    }, wait)
十七、十七、
    // 如果当前传递进来的是 true 就表示我们需要立即执行
    // 如果想要实现只在第一次执行,那么可以添加上 timer 为 null 做为判断
    // 因为只要 timer 为 Null 就意味着没有第二次....点击
    init ? handle.call(self, ...args) : null
  }
}

// 定义事件执行函数
function btnClick(ev) {
  console.log('点击了1111', this, ev)
}

// 当我们执行了按钮点击之后就会执行...返回的 proxy
let oBtn = document.getElementById('btn')
oBtn.onclick = myDebounce(btnClick, 200, false)

2.节流实现

节流:我们这里的节流指的就是在自定义的一段时间内让事件进行触发

核心的思想,就是给事件赋值函数的时候不直接赋值相应的直行函数,而是赋值一个代理函数

以滚动事件触发为例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流函数实现</title>
  <style>
    body {
      height: 5000px;
    }
  </style>
</head>

<body>
  <script>
    // 节流:我们这里的节流指的就是在自定义的一段时间内让事件进行触发

    function myThrottle(handle, wait) {
      if (typeof handle !== 'function') throw new Error('handle must be an function')
      if (typeof wait === 'undefined') wait = 400

      let previous = 0  // 定义变量记录上一次执行时的时间 
      let timer = null  // 用它来管理定时器

      return function proxy(...args) {
        let now = new Date() // 定义变量记录当前次执行的时刻时间点
        let self = this
        let interval = wait - (now - previous)

        if (interval <= 0) {
          // 此时就说明是一个非高频次操作,可以执行 handle 
          clearTimeout(timer)
          timer = null
          handle.call(self, ...args)
          previous = new Date()
        } else if (!timer) {
          // 当我们发现当前系统中有一个定时器了,就意味着我们不需要再开启定时器
          // 此时就说明这次的操作发生在了我们定义的频次时间范围内,那就不应该执行 handle
          // 这个时候我们就可以自定义一个定时器,让 handle 在 interval 之后去执行 
          timer = setTimeout(() => {
            clearTimeout(timer) // 这个操作只是将系统中的定时器清除了,但是 timer 中的值还在
            timer = null
            handle.call(self, ...args)
            previous = new Date()
          }, interval)
        }
      }

    }

    // 定义滚动事件监听
    function scrollFn() {
      console.log('滚动了')
    }

    // window.onscroll = scrollFn
    window.onscroll = myThrottle(scrollFn, 600)
  </script>
</body>

</html>

每一次触发都会开辟一个全新的上下文,因此这里会造成性能上的消耗,造成不必要的资源浪费,因为我们也没必要有这么多的高频触发。

谷歌浏览器会在每5到6ms的时候进行一次事件监听,但是我们不想触发太频繁,因此这里我们想要400ms的时候再去触发,就可以写节流.

这里有一些注意点

  1. 写timeout的原因是,当我们处于高频出发时间段内的时候,我们是让他不执行,但是如果后续没有再触发,其实最后一次我们是需要相应的数据的,所以写timeout延迟触发最后一次
  2. 在设立定时器的时候需要将之前的定时器进行清除,不然会设立多个,只会延迟后面的操作,却不会不执行
  3. 谷歌浏览器的触发时间点正好跟咱们写的节流函数出发点时间重合的时候,interval <=0成立,但是tiemer确实不为空,定时函数就没有再重新设立,所以需要在上面的判断中,将timer重新清除,这样才可以形成一个新的定时器,为后续使用
  4. 这里的节流跟我之前看到的节流的实现可能不太一样,但是这一版本确实完善一些,主体思想都是一样的
    十八、减少判断层级

在编写代码的过程中避免不了会用到if else判断,但是多层的if判断会影响代码性能,这时我们都可以提前去return掉无效条件,达到嵌套层级的优化效果。

实例原代码:

/**
 * 编写一些章节会员判断
 * @param part 当前章节
 * @param chapter 当前章节数
 */
function doSomeThing(part,chapter){
    const parts=['ES2016','工程化','Vue','React','Node'];
    if (part){
        if (parts.includes(part)){
            console.log('当前属于可读模块')
            if (chapter > 5){
                console.log('该模块是付费内容,您需要提供一个vip身份')
            }
        }
    }else{
        console.log('请确认模块信息')
    }
}
doSomeThing('ES2016',6)

简化:

function doSomeThing2(part, chapter) {
    const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node'];
    if (!part) {
        console.log('请确认模块信息')
        return
    }
    if (!parts.includes(part)) return
    console.log('当前属于可读模块')
    if (chapter > 5) {
        console.log('该模块是付费内容,您需要提供一个vip身份')
    }
}
doSomeThing2('ES2016', 6)

在JSBench中进行测试:
在这里插入图片描述
可以看到下面的ops会大一些。

十九、减少循环体活动

主要说功能,而不是哪种实现方式,循环次数固定的情况下,放在循环体里的内容越多,执行效率越慢。有几种优化思路:

1.每次循环都要用到的不变的值,都抽离到循环外面去完成,类似于数据缓存。

例子:

var test = () =>{
    var i;
    var arr = ['zce','38','前端致胜'];
    for (i = 0; i <arr.length ; i++){
        console.log(arr[i])
    }
}
test()

优化:

var test2 = () =>{
    var i;
    var arr = ['zce','38','前端致胜'];
    var len = arr.length;//减少对象的访问层级
    for (i = 0; i <len ; i++){
        console.log(arr[i])
    }
}
test2()

运行比较:
在这里插入图片描述
2.若是我们对于输出顺序没有要求的话,也可以换成while循环,反向进行遍历。

var test3 = () =>{
    var arr = ['zce','38','前端致胜'];
    var len = arr.length;//减少对象的访问层级
    while(len--){
        console.log(arr[len])
    }
}
test3()

在这里插入图片描述

  • 代码量变少了
  • 从后往前,可以少做很多条件判断
  • 效率上如图也提高了
    二十、减少循环体活动

不同的数据声明方式在性能上面的关系和表现。

引用类型为例:

构造函数的方式:

let test = () =>{
    let obj = new Object();//构造函数的方式
    obj.name = 'zce'
    obj.age = 38
    obj.slogan = '前端致胜'
    return obj
}
console.log(test())

字面量方式:

let test2 = () =>{
    //字面量方式
    let obj = {
        name : 'zce',
        age : 38,
        slogan : '前端致胜'
    };
    return obj
}
console.log(test2())

对比:
在这里插入图片描述
还是能够看出来字面量的方式效率上更快,有一个较大的差异,原因:

  1. 构造函数声明的方式其实是调用一个函数,而下面字面量的方式是开辟一个空间
  2. 且代码多
    基础数据类型:
var str1 = 'zce说前端致胜'
var str2 = new String('zce说前端致胜')
console.log(str1)
console.log(str2)

//zce说前端致胜
//[String: 'zce说前端致胜']

对比:
在这里插入图片描述
差距在5%以上,相较于引用类型数据,差距会更大,因为此时代码量是一致的,而上面是基础数据声明,下面是对象声明。

此时有一个细节,若是我们对这个字符串进行一些方法的调用的时候,上面会默认先转化为对象,然后再调用,而下面是直接调用,还是提倡用上面的方式,因为下面的方式创建的时候必然会有一些用不到的空间被占用和消耗。

总结:尽量采取字面量进行声明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1589837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PSpice软件快速入门系列--如何进行AC Sweep交流扫描

背景介绍&#xff1a;PSpice仿真分析类型通常有四种&#xff0c;分别是时域分析、直流特性扫描、交流特性扫描/噪声分析、直流工作点计算。交流扫描分析是线性分析&#xff0c;可对电路性能因输入信号频率不同而变化的过程进行分析&#xff0c;获得电路的幅频响应和相频特性以及…

探索工业AI智能摄像机的卓越性能!

​ 在当今快速发展的工业智能化领域&#xff0c;上海晶珩的工业AI智能摄像机系列以其卓越的性能和多功能性在国内外备受关注&#xff08;文末有国外工程师的评测链接&#xff09;。搭载Raspberry Pi CM4支持的ED-AIC2000和ED-AIC2100系列旨在广泛应用&#xff0c;涵盖从简单的条…

俄罗斯yandex广告推广如何投放?

俄罗斯作为欧亚大陆的重要经济体&#xff0c;拥有庞大的互联网用户基数&#xff0c;其中Yandex作为该地区最主要的搜索引擎&#xff0c;无疑是触及目标客户群的关键渠道。云衔科技凭借专业优势与实战经验&#xff0c;为企业提供一站式Yandex广告开户及全程代运营解决方案&#…

MySQL 04-EMOJI 表情与 UTF8MB4 的故事

拓展阅读 MySQL View MySQL truncate table 与 delete 清空表的区别和坑 MySQL Ruler mysql 日常开发规范 MySQL datetime timestamp 以及如何自动更新&#xff0c;如何实现范围查询 MySQL 06 mysql 如何实现类似 oracle 的 merge into MySQL 05 MySQL入门教程&#xff0…

【Android surface 】二:源码分析App的surface创建过程

文章目录 画布surfaceViewRoot的创建&setView分析setViewrequestLayoutViewRoot和WMS的关系 activity的UI绘制draw surfacejni层分析Surface无参构造SurfaceSessionSurfaceSession_init surface的有参构造Surface_copyFromSurface_writeToParcelSurface_readFromParcel 总结…

从商品图到海报生成 京东广告AIGC创意技术应用

一、前言 电商广告图片不仅能够抓住消费者的眼球&#xff0c;还可以传递品牌核心价值和故事&#xff0c;建立起与消费者之间的情感联系。然而现有的广告图片大多依赖人工制作&#xff0c;存在效率和成本的限制。尽管最近 AIGC 技术取得了卓越的进展&#xff0c;但其在广告图片…

嵌入式中常用的巧妙方法 - (汇总)

概述 做项目&#xff0c;掌握以下方法&#xff0c;可提高开发效率&#xff0c;把时间全部放在需求上。 1、快速获取结构体成员大小 #include <stdio.h> // 获取结构体成员大小 #define GET_MEMBER_SIZE(type, member) sizeof(((type*)0)->member)// 获取结构体成…

2024 大模型面试指南:兄弟们,冲啊

前言 老宋这俩月又跳槽了&#xff0c;自从去年从百度出来来到新公司&#xff0c;躺了一年&#xff0c;最近因为大模型技术发展&#xff0c;重新有了奋斗的方向和动力。 大模型的诞生必然会重塑整个 NLP 方向&#xff0c;因此&#xff0c;必须参与到这波浪潮中&#xff0c;果然…

HTTP快速面试笔记(速成版)

文章目录 1. HTTP概述1.1 HTTP简介1.2 HTTP的版本1.3 URL语法简介 2. HTTP报文2.1 HTTP报文格式2.2 HTTP的方法&#xff08;Method&#xff09;2.3 HTTP响应码2.4 HTTP请求头与响应头 3. HTTPS详解3.1 HTTPS介绍3.2 与HTTPS相关的加解密知识3.3 HTTPS交互流程 参考资料 1. HTTP…

2 万字 42 道Java经典面试题总结(2024修订版)- Java集合篇

目录 1、Java中常用的集合有哪些&#xff1f;2、Collection 和 Collections 有什么区别&#xff1f;3、为什么集合类没有实现 Cloneable 和 Serializable 接口&#xff1f;4、数组和集合有什么本质区别&#xff1f;5、数组和集合如何选择&#xff1f;6、list与Set区别7、HashMa…

基于深度学习的人脸表情识别系统(PyQT+代码+训练数据集)

基于深度学习的人脸表情识别系统&#xff08;PyQT代码训练数据集&#xff09; 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现 前言 本项目是基于mini_Xception深度学习网络模型的人脸表情识别系统&#x…

el-upload文件缩略图只显示一张图片

采用elementui库vue2版本&#xff0c;flask后端 el-upload组件上传一张图片之后不在出现新增加号 可以实现

基于公共转点的Alpha shapes有序边缘点提取

1、原理介绍 由Edelsbrunner H提出的alpha shapes算法是一种简单、有效的快速提取边界点算法。其克服了点云边界点形状影响的缺点,可快速准确提取边界点,其原理如下:对于任意形状的平面点云,若一个半径为a的圆,绕其进行滚动,其滚动的轨迹形成的点为轮廓点。需要注意的是,…

深入理解计算机系统 家庭作业 2.84

这题没有这个要求所以可以用 ? > : < 这种运算 以下代码用的是位级运算.因为我误解了题意 呜呜呜 想看用判断的代码请自行百度 ((((ux<<9>>9)<<((ux<<1>>24)-127)) - ((uy<<9>>9)<<((uy<<1>>24)-127)))>…

TMS320F280049 EPWM模块--TZ子模块(6)

下图是TZ子模块在epwm中的位置&#xff0c;可以看到TZ子模块接收内外部多种信号&#xff0c;经过处理后生成最终epwm波形&#xff0c;然后通过gpio向外发出。 TZ的动作有4个&#xff1a;拉高/拉低/高阻/不变。 TZ的内部框图见下图&#xff0c;可以看出&#xff1a; 1&#xf…

每日一题 — 水果成篮

思路&#xff1a; 通过阅读上面文字得出问题&#xff1a;就去只有两个种类的最大长度的连续子数组&#xff0c;这时我们可以想到用哈希表来存储数据&#xff0c;记录数据的种类和每个种类的数量。 解法一&#xff1a;暴力递归&#xff08;right每次遍历完都回退&#xff09; 解…

windows本地运行dreamtalk踩坑总结

dreamtalk是一个语音图片转视频的一个工具&#xff0c;就是给一段语音加一个头像图片&#xff0c;然后生成一段头像跟语音对口型的视频&#xff0c;其实还是很有意思的&#xff0c;最近阿里发布了一个类似的模型&#xff0c;但是还没开源&#xff0c;从展示视频看&#xff0c;阿…

Day31:贪心 LeedCode 455.分发饼干 376. 摆动序列 53. 最大子序和 蓝桥杯.填充

贪心算法一般分为如下四步&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 做题的时候&#xff0c;只要想清楚 局部最优 是什么&#xff0c;如果推导出全局最优&#xff0c;其实就够了。 假设你是一位很棒的家…

场景文本检测识别学习 day04(目标检测的基础概念)

经典的目标检测方法 one-stage 单阶段法&#xff1a;YOLO系列 one-stage方法&#xff1a;仅使用一个CNN&#xff0c;直接在特征图上预测每个物体的类别和边界框输入图像之后&#xff0c;使用CNN网络提取特征图&#xff0c;不加入任何补充&#xff08;锚点、锚框&#xff09;&…

QT系列教程(2) 创建项目和编译

新建Qt Widgets应用 我们启动qt creator 创建项目&#xff0c;选择Qt Widgets应用 接下来选择项目目录&#xff0c;项目名字就叫helloworld 构建系统选择qmake 我们创建一个名字为HelloDialog的类&#xff0c;继承于QDialog 构建套件选择你们安装的就行了&#xff0c;我这里选…