学习笔记(六)——JavaScript 性能优化

内存管理

由开发者主动申请、使用、释放内存空间

JavaScript中的内存管理是自动的

  • 申请内存空间
    • JavaScript中没有提供内存空间申请的API
    • 当定义一个变量,变量被声明赋值时,系统自动分配相应的内存空间
  • 使用内存空间
    • 对变量进行修改赋值等操作
  • 释放内存空间
    • JavaScript中没有提供内存空间释放的API
    • 手动将变量赋值为null,使内存空间不再被变量所引用,内存将在下次垃圾回收过程中被释放

垃圾回收

  • 什么是JavaScript中的垃圾?
    • 对象不再被引用时,是垃圾
    • 对象不能从根上被访问到时,是垃圾
  • 可达对象
    • 可以访问到的对象,即为可达对象
    • 可达的标准是从根出发能否被访问到
    • JavaScript中的根可以理解为全局变量对象

GC算法介绍

GC是垃圾回收机制的缩写

GC可以找到内存中的垃圾,并释放和回收内存空间

  • GC的垃圾是什么
    • 程序不再使用的对象
    • 程序不能再访问到的对象
  • 常见GC算法
    • 引用计数
    • 标记清除
    • 标记整理
    • 分代回收

引用计数算法

核心思想是,设置引用数,判断当前引用数是否为0,为0则进行垃圾回收

引用关系发生改变时,引用计数器修改引用数

  • 优点
    • 发现垃圾立即回收
    • 最大限度减少程序暂停
  • 缺点
    • 无法回收循环引用的对象(引用计数不为0)
    • 资源开销大

标记清除算法

核心思想是,分为标记和清除两个阶段

先遍历所有对象,找到活动对象(可达可访问)进行标记

再遍历所有对象,回收未被标记的对象,并清除已被标记的对象的标记

  • 优点
    • 可以回收循环引用对象(不可达对象不会被标记)
  • 缺点
    • 内存空间碎片化(内存地址不连续),浪费空间

标记整理算法

标记整理算法是标记清除算法的增强

标记阶段与标记清除算法相同

清除阶段会先进行整理操作,移动对象位置,使内存地址连续

V8引擎

V8是一款主流JavaScript执行引擎

采用即时编译

内存设有上限

  • V8垃圾回收策略
    • 采用分代回收思想,将内存分为新生代、老生代,针对不同对象采用不同算法
  • V8中常用的GC算法
    • 分代回收
    • 空间复制
    • 标记清除
    • 标记整理
    • 增量增量
  • V8内存分配
    • 内存空间分为两个部分
    • 小空间(64位系统32M|32位系统16M)用于存放新生代对象(存活时间较短的对象)
    • 大空间(64位系统1.4G|32位系统700M)用于存放老生代对象(存活时间较长的对象,例如全局对象下存放的对象,以及闭包中存放的对象等)
  • 新生代对象回收
    • 采用空间复制 + 标记整理算法
    • 将新生代内存区分为大小相等的两个空间,使用空间为From,空闲空间为To
    • 触发GC时,使用标记整理算法对From空间进行标记,并将活动对象拷贝到To空间
    • 回收释放From空间,并与To空间进行互换
    • 新生代对象从From空间拷贝到To空间过程中可能出现晋升操作(将新生代对象移动至老生代对象存储区域)
      • 一轮GC后还存活的新生代对象将晋升
      • To空间使用率超过25%时
  • 老生代对象回收
    • 采用标记清除、标记整理、增量标记算法
    • 先使用标记清除完成垃圾空间回收(回收速度快)
    • 当出现晋升时,如果空间不足,会使用标记整理算法进行空间优化
    • 采用增量标记进行效率优化
      • 垃圾回收会阻塞JavaScript程序代码执行
      • 将垃圾回收的对象进行分段,使垃圾回收过程与程序代码执行交替进行,避免需要回收的对象较多时(上限1.5G,回收只需1秒)长时间阻塞程序执行
  • 新生代回收 vs 老生代回收
    • 新生代空间小,使用空间换时间(空间复制算法)
    • 老生代空间大,不适合使用空间复制算法

Performance工具介绍

浏览器中查看web程序执行性能的辅助工具,提供多种监控方式

  • 使用方式
    • 打开浏览器,输入目标网址
    • 打开开发人员工具,进入Performance工具选项页面,点击录制
    • 访问目标网址,进行用户操作
    • 停止录制
    • 查看性能监控信息

监控内存的几种方式

  • 浏览器任务管理器
    • 快捷键Shift + Esc (Chrome) 查看JavaScript内存使用列
  • Timeline时序图记录
    • Performance工具中录制查看
  • 堆快照查找分离DOM
    • 什么是分离DOM?
      • 脱离DOM树,未被代码引用的DOM为垃圾,会被回收,仍被代码引用的DOM,即为分离DOM
    • 使用浏览器开发者选项中的内存工具Memory,获取堆快照Heap Snapshot
    • 过滤关键字,查找Detached,结果中的Detached HTML**Element,即为分离DOM
  • 判断是否存在频繁垃圾回收
    • 任务管理器或Timeline中查看内存是否频繁变化

代码优化

  • 使用基于benchmark.js的jsperf来评估JavaScript代码的性能

  • 慎用全局变量

    • 全局对象定义在全局上下文,一直存在与全局上下文执行栈,直到程序退出才会释放
    • 局部作用域出现同名变量容易出现变量污染
    • 使用局部变量的代码,性能要优于使用全局变量的代码
  • 缓存全局变量

    • 缓存全局变量,再使用缓存的变量进行代码操作,性能要优于直接使用全局对量,但性能差距非常小
  • 通过原型对象添加附加方法

    • 构造函数中添加方法,每个实例对象中都会创建一个相同的方法,占用内存
    • 构造函数原型对象上添加方法,每个实例共享一个方法,性能更好
  • 避开闭包陷阱

    • 闭包在外部作用域访问内部作用域的数据,具有指向内部的引用

    • 闭包外层函数调用完成后,内部函数及变量仍被引用,未被释放,容易引起内存泄露

    • 闭包使用完成后,手动将引用清空(赋值为null)

      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      <body>
          <button id="btn">hello</button>
          <script>
              // function foo() {
              //     let btn = document.getElementById('btn')
              //     btn.onclick = function() {
              //         console.log(btn.id)
              //     }
              // }
              // foo()
              function foo() {
                  let btn = document.getElementById('btn')
                  btn.onclick = function() {
                      console.log(this.id)
                  }
                  btn = null
              }
              foo()
          </script>
      </body>
      </html>
      
  • 避免属性访问方法使用

    • JavaScript的面向对象的属性是直接对外暴露的,并不需要属性的访问方法(类似Java的getXXX、setXXX方法)
    • 属性访问方法相当于在构造函数中添加实例方法
    • 直接使用属性访问,在性能上,比使用属性访问方法来访问属性更好
  • For循环优化

    • 对要遍历的数组的length进行提前获取并缓存

      let array = [1, 2, 3, 4, 5]
      
      for (let i = 0, len = array.length; i < len; i++) {
          console.log(i)
      }
      
  • 选择最优的循环方法

    在数据量较大的情况下,执行效率排序

    while > for > forEach > for...of > map > for...in

    • for
    • forEach
      • 无法break或return中断
    • for...of
    • for...in
    • map
    • while
  • 文档碎片(document fragment)优化节点添加

    添加dom节点必然会产生回流和重绘,使用文档碎片,将多次节点添加统一成一次,可以减少回流reflow和重绘repaint

    • 使用 document.createDocumentFragment 创建文档碎片,并将要添加的节点append到文档碎片中,最后append到body
    const fragment = document.createDocumentFragment()
            for (let i = 0; i < 10; i++) {
                fragment.append(document.createElement('div'))
            }
            document.body.append(fragment)
    
  • 克隆优化节点添加

    • 使用element.cloneNode(false) 通过克隆的方式来创建节点,比使用 document.createEleemnt() 性能好一些,但差距不是特别大
  • 直接字面量替换new Object

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343