找回密码
 立即注册
查看: 381|回复: 0

JavaScript性能优化

[复制链接]
发表于 2022-2-6 14:18 | 显示全部楼层 |阅读模式
内存管理


  • 内存:由可读写单元组成,表示一片可操作空间
  • 管理:人为的去操作一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间
  • 管理流程:申请-使用-释放
// 申请
let obj = {}
// 使用
obj.name = 'jal'
// 释放
obj = null
垃圾回收与常见GC算法

JavaScript中的垃圾


  • JavaScript中内存管理是自动的
  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾
JavaScript中的可达对象


  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根出发是否能够被找到
  • JavaScript中的根就可以理解为是全局变量对象
let obj = {name: 'xm'} // name是可达对象
let ali = obj // 两个对象可以可达name
obj = null // name依旧可达
![image-20220119145818217](/Users/panda/Library/Application Support/typora-user-images/image-20220119145818217.png)
GC算法

GC定义与应用
GC就是垃圾回收机制的简写
GC可以找到内存中的垃圾、并释放和回收空间
GC里的垃圾是什么
从需求角度考虑:程序中不再需要使用对象
从运行过程中:变量是否能够被引用到
GC算法是什么
GC是一种机制,垃圾回收器完成具体的工作
工作的内容就是查找垃圾释放空间、回收空间
算法就是工作时查找和回收所遵守的规则
常见GC算法


  • 引用计数
  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器
  • 引用关系改变时修改引用数字
  • 引用数字为0时立即回收
##### 优点

  • 发现垃圾时立即回收
  • 最大限度减少程序暂停
##### 缺点

  • 无法回收循环引用的对象
  • 标记清除
  • 核心思想:分标记和清除两个阶段完成
  • 遍历所有对象找标记活动对象
  • 遍历所有对象清除没有标记对象
  • 回收相应的空间
##### 优点

  • 可以回收循环引用的对象
##### 缺点

  • 空间的碎片化,地址不连续,浪费空间
  • 不会立即回收垃圾对象
  • 回收垃圾时,程序停止工作
  • 标记整理
  • 可以看做标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段先执行整理,移动对象位置
##### 优点

  • 减少碎片化空间
##### 缺点

  • 不会立即回收垃圾对象
  • 分代回收
V8引擎的垃圾回收


  • V8是一款主流的JavaScript执行引擎
  • V8采用即时编译,很快
  • V8内存设有上限,64位系统不超过1.5G,32位不超过800M
  • V8垃圾回收策略 采用分代回收的思想 内存分为新生代、老生代 针对不同对象采用不同算法
  • V8中的常用GC算法 分代回收 空间复制 标记清除 标记整理 标记增量
V8如何回收新生代对象


  • V8内存分配 V8内存空间一分为二 小空间用于存储新生代对象( 32M | 16M ) 新生代指的是存活时间较短的对象
  • 新生代对象回收实现 回收过程采用复制算法+标记整理 新生代内存区分为二个等大小空间 使用空间为From,空闲空间为To 活动对象存储于From空间 标记整理后将活动对象拷贝至To From与To交换空间完成释放
  • 回收细节说明 拷贝过程中可能出现晋升 晋升就是将新生代对象移动至老生代 一轮GC还存活的新生代需要晋升 To空间的使用率超过25%
V8如何回收老生代对象


  • 老生代对象说明 老生代对象存放在右侧老生代区域 64位系统1.4G,32位系统700M 老生代对象就是指存活时间较长的对象:全局对象、闭包数据
  • 老生代对象回收实现 主要采用标记清除、标记整理、增量标记算法 首先使用标记清除完成垃圾空间的回收 采用标记整理进行空间优化 采用增量标记进行效率优化
  • 细节对比 新生代区域垃圾回收使用空间换时间 老生代区域垃圾回收不适合复制算法
  • 标记增量:垃圾回收阻塞JS执行,将一整段的垃圾回收分成几个小段 垃圾回收和程序执行交替执行
Performance工具使用


  • 界定内存问题的标准 内存泄漏:内存使用持续升高 内存膨胀:在多数设备上都存在性能问题 频繁垃圾回收:通过内存变化图进行分析
  • 浏览器任务管理器(shift+esc)
  • Timeline时序图记录
  • 堆快照查找分离DOM 什么是分离DOM 界面元素存活在DOM树上 垃圾对象时的DOM节点 分离状态的DOM节点
  • 判断是否存在频繁的垃圾回收
  • 为什么确定频繁垃圾回收 GC工作时应用程序是停止的 频繁且过长的GC会导致应用卡死 用户使用中感知应用卡顿
  • 确定频繁的垃圾回收 Timeline中频繁的上升下降 任务管理器中数据频繁的增加减小
堆栈处理

见堆栈处理
优化方案

变量局部化

这样可以提高代码的执行效率( 减少了数据访问时需要查找的路径 )
数据的存储和读取


全局变量定义在全局执行上下文,是所有作用域链的顶端 全局执行上下文一直存在于上下文执行栈,直到程序退出 如果某个局部作用域出现了同名变量则会遮蔽或污染全局
减少访问层级

// 减少访问层级
// var obj = {
//   age: 18,
//   methods: {
//     m1: {
//       name: '',
//       time: 100
//     },
//     m2: {
//       name: '',

//     }
//   }
// }

function Person() {
  this.name = 'zce'
  this.age = 40
}

let p1 = new Person()
console.log(p1.age)

function Person() {
  this.name = 'zce'
  this.age = 40
  this.getAge = function () {
    return this.age
  }
}

let p1 = new Person()
console.log(p1.getAge())
缓存数据

缓存数据:对于需要多次使用的数据进行提前保存,后续进行使用
var oBox = document.getElementById('skip')

// 假设在当前的函数体当中需要对 className 的值进行多次使用,那么我们就可以将它提前缓存起来
function hasClassName(ele, cls) {
  console.log(ele.className)
  return ele.className == cls
}
console.log(hasClassName(oBox, 'skip'))


function hasClassName(ele, cls) {
  var clsName = ele.className
  console.log(clsName)
  return clsName == cls
}
console.log(hasClassName(oBox, 'skip'))

  • 减少声明和语句数(词法 语法)
  • 缓存数据(作用域链查找变快)
防抖与节流

为什么需要防抖和节流:


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

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

  • 浏览器默认情况下都会有自己的监听事件间隔( 4~6ms),如果检测到多次事件的监听执行,那么就会造成不必要的资源浪费
  • 前置的场景: 界面上有一个按钮,我们可以连续多次点击
  • 防抖:对于这个高频的操作来说,我们只希望识别一次点击,可以人为是第一次或者是最后一次
  • 节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,按着我们定 义的频率减少触发的次数
防抖函数

<!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>
</head>

<body>
  <button id="btn">点击</button>
  <script>
    var oBtn = document.getElementById('btn')
    // oBtn.onclick = function () {
    //   console.log('点击了')
    // }

    /**
     * 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次都无用
      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
    oBtn.onclick = myDebounce(btnClick, 200, false)
    // oBtn.onclick = btnClick()  // this ev

  </script>
</body>

</html>节流函数



<!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>

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 19:54 , Processed in 0.094459 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表