「硬核JS」你的程序中可能存在内存泄漏

发表于 2年以前  | 总阅读数:377 次

写在前面

想来很多同学看到内存泄漏,内心直接会跳出两个字:闭包!!!再让你说点其它的估计就噤声了。如果你对内存泄漏的了解仅限于闭包,那真的是应该仔细看此文了,闭包可能会造成内存泄漏,但是内存泄漏并不是只有闭包,它只是内存泄漏的引子之一罢了。

写的程序运行一段时间后慢慢变卡甚至要崩溃了?

如题,你的程序中可能存在内存泄漏,说到内存泄漏,建议先读[「硬核JS」你真的懂垃圾回收机制吗] 一文,然后再来看此文会比较通透,毕竟垃圾回收和内存泄漏是因果关系,垃圾被回收了啥事没有,垃圾没被回收就是内存泄漏。

此文我们会介绍内存泄漏的相关概念和引起内存泄漏的一些问题,还会着重给大家介绍内存泄漏的排查、定位及修复方法(学到即可用到),最后还简单扩展了下前端内存三大件的其他两件内存膨胀和频繁 GC 的概念。

什么是内存泄漏

引擎中有垃圾回收机制,它主要针对一些程序中不再使用的对象,对其清理回收释放掉内存。

那么垃圾回收机制会把不再使用的对象(垃圾)全都回收掉吗?

其实引擎虽然针对垃圾回收做了各种优化从而尽可能的确保垃圾得以回收,但并不是说我们就可以完全不用关心这块了,我们代码中依然要主动避免一些不利于引擎做垃圾回收操作,因为不是所有无用对象内存都可以被回收的,那当不再用到的对象内存,没有及时被回收时,我们叫它内存泄漏(Memory leak)。

常见的内存泄漏

代码不规范,同事两行泪,接下来我们看看会引起内存泄漏的一些常见案例。

不正当的闭包

闭包就是函数内部嵌套并 return 一个函数???这是大多数人认为的闭包,好吧,它确实也是,我们来看看几本 JS 高光书中的描述:

  • JavaScript高级程序设计:闭包是指有权访问另一个函数作用域中的变量的函数
  • JavaScript权威指南:从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链
  • 你不知道的JavaScript:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行

按照上面三本书中的描述,那闭包所涉及的的范围就比较广了,我们这里暂时不去纠结闭包的定义,就以最简单、大家都认可的闭包例子来看闭包:

function fn1(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log('hahaha')
  }
}
let fn1Child = fn1()
fn1Child()

上例是闭包吗?它造成内存泄漏了吗?

显然它是一个典型闭包,但是它并没有造成内存泄漏,因为返回的函数中并没有对 fn1 函数内部的引用,也就是说,函数 fn1 内部的 test 变量完全是可以被回收的,那我们再来看:

function fn2(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log(test)
    return test
  }
}
let fn2Child = fn2()
fn2Child()

上例是闭包吗?它造成内存泄漏了吗?

显然它也是闭包,并且因为 return 的函数中存在函数 fn2 中的 test 变量引用,所以 test 并不会被回收,也就造成了内存泄漏。

那么怎样解决呢?

其实在函数调用后,把外部的引用关系置空就好了,如下:

function fn2(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log(test)
    return test
  }
}
let fn2Child = fn2()
fn2Child()
fn2Child = null

“ 减少使用闭包,闭包会造成内存泄漏。。。”

醒醒,这句话是过去式了,它的描述不准确,So,应该说不正当的使用闭包可能会造成内存泄漏。

隐式全局变量

我们知道 JavaScript 的垃圾回收是自动执行的,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间。

再来看全局变量和局部变量,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器会识别并释放它们。但是对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以全局变量通常不会被回收,我们使用全局变量是 OK 的,但同时我们要避免一些额外的全局变量产生,如下:

function fn(){
  // 没有声明从而制造了隐式全局变量test1
  test1 = new Array(1000).fill('isboyjc1')

  // 函数内部this指向window,制造了隐式全局变量test2
  this.test2 = new Array(1000).fill('isboyjc2')
}
fn()

调用函数 fn ,因为 没有声明 和 函数中this 的问题造成了两个额外的隐式全局变量,这两个变量不会被回收,这种情况我们要尽可能的避免,在开发中我们可以使用严格模式或者通过 lint 检查来避免这些情况的发生,从而降低内存成本。

除此之外,我们在程序中也会不可避免的使用全局变量,这些全局变量除非被取消或者重新分配之外也是无法回收的,这也就需要我们额外的关注,也就是说当我们在使用全局变量存储数据时,要确保使用后将其置空或者重新分配,当然也很简单,在使用完将其置为 null 即可,特别是在使用全局变量做持续存储大量数据的缓存时,我们一定要记得设置存储上限并及时清理,不然的话数据量越来越大,内存压力也会随之增高。

var test = new Array(10000)

// do something

test = null

游离DOM引用

考虑到性能或代码简洁方面,我们代码中进行 DOM 时会使用变量缓存 DOM 节点的引用,但移除节点的时候,我们应该同步释放缓存的引用,否则游离的子树无法释放。

<div id="root">
  <ul id="ul">
    <li></li>
    <li></li>
    <li id="li3"></li>
    <li></li>
  </ul>
</div>
<script>
  let root = document.querySelector('#root')
  let ul = document.querySelector('#ul')
  let li3 = document.querySelector('#li3')

  // 由于ul变量存在,整个ul及其子元素都不能GC
  root.removeChild(ul)

  // 虽置空了ul变量,但由于li3变量引用ul的子节点,所以ul元素依然不能被GC
  ul = null

  // 已无变量引用,此时可以GC
  li3 = null
</script>

如上所示,当我们使用变量缓存 DOM 节点引用后删除了节点,如果不将缓存引用的变量置空,依然进行不了 GC,也就会出现内存泄漏。

假如我们将父节点置空,但是被删除的父节点其子节点引用也缓存在变量里,那么就会导致整个父 DOM 节点树下整个游离节点树均无法清理,还是会出现内存泄漏,解决办法就是将引用子节点的变量也置空,如下图:

遗忘的定时器

程序中我们经常会用到计时器,也就是 setTimeoutsetInterval,先来看一个例子:

// 获取数据
let someResource = getData()
setInterval(() => {
  const node = document.getElementById('Node')
 if(node) {
    node.innerHTML = JSON.stringify(someResource))
 }
}, 1000)

上面是我随便 copy 的一个小例子,其代码中每隔一秒就将得到的数据放入到 Node 节点中去,但是在 setInterval 没有结束前,回调函数里的变量以及回调函数本身都无法被回收。

什么才叫结束呢?也就是调用了 clearInterval。如果没有被 clear 掉的话,就会造成内存泄漏。不仅如此,如果回调函数没有被回收,那么回调函数内依赖的变量也没法被回收。所以在上例中,someResource 就没法被回收。

同样,setTiemout 也会有同样的问题,所以,当不需要 interval 或者 timeout 时,最好调用 clearInterval 或者 clearTimeout来清除,另外,浏览器中的 requestAnimationFrame 也存在这个问题,我们需要在不需要的时候用 cancelAnimationFrame API 来取消使用。

遗忘的事件监听器

当事件监听器在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,其中引用的变量或者函数都被认为是需要的而不会进行回收,如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样就造成意外的内存泄漏。

我们就拿 Vue 组件来举例子,React 里也是一样的:

<template>
  <div></div>
</template>

<script>
export default {
  created() {
    window.addEventListener("resize", this.doSomething)
  },
  beforeDestroy(){
    window.removeEventListener("resize", this.doSomething)
  },
  methods: {
    doSomething() {
      // do something
    }
  }
}
</script>

遗忘的监听者模式

监听者模式想必我们都知道,不管是 Vue 、 React 亦或是其他,对于目前的前端开发框架来说,监听者模式实现一些消息通信都是非常常见的,比如 EventBus. . .

当我们实现了监听者模式并在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,其中引用的变量或者函数都被认为是需要的而不会进行回收,如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样也会造成意外的内存泄漏。

还是用 Vue 组件举例子,因为比较简单:

<template>
  <div></div>
</template>

<script>
export default {
  created() {
    eventBus.on("test", this.doSomething)
  },
  beforeDestroy(){
    eventBus.off("test", this.doSomething)
  },
  methods: {
    doSomething() {
      // do something
    }
  }
}
</script>

如上,我们只需在 beforeDestroy 组件销毁生命周期里将其清除即可。

遗忘的Map、Set对象

当使用 MapSet 存储对象时,同 Object 一致都是强引用,如果不将其主动清除引用,其同样会造成内存不自动进行回收。

如果使用 Map ,对于键为对象的情况,可以采用 WeakMapWeakMap 对象同样用来保存键值对,对于键是弱引用(注:WeakMap 只对于键是弱引用),且必须为一个对象,而值可以是任意的对象或者原始值,由于是对于对象的弱引用,不会干扰 Js 的垃圾回收。

如果需要使用 Set 引用对象,可以采用 WeakSetWeakSet 对象允许存储对象弱引用的唯一值,WeakSet 对象中的值同样不会重复,且只能保存对象的弱引用,同样由于是对于对象的弱引用,不会干扰 Js 的垃圾回收。

这里可能需要简单介绍下,谈弱引用,我们先来说强引用,之前我们说 JS 的垃圾回收机制是如果我们持有对一个对象的引用,那么这个对象就不会被垃圾回收,这里的引用,指的就是 强引用 ,而弱引用就是一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,因此可能在任何时刻被回收。

不明白?来看例子就晓得了:

// obj是一个强引用,对象存于内存,可用
let obj = {id: 1}

// 重写obj引用
obj = null 
// 对象从内存移除,回收 {id: 1} 对象

上面是一个简单的通过重写引用来清除对象引用,使其可回收。

再看下面这个:

let obj = {id: 1}
let user = {info: obj}
let set = new Set([obj])
let map = new Map([[obj, 'hahaha']])

// 重写obj
obj = null 

console.log(user.info) // {id: 1}
console.log(set)
console.log(map)

此例我们重写 obj 以后,{id: 1} 依然会存在于内存中,因为 user 对象以及后面的 set/map 都强引用了它,Set/Map、对象、数组对象等都是强引用,所以我们仍然可以获取到 {id: 1} ,我们想要清除那就只能重写所有引用将其置空了。

接下来我们看 WeakMap 以及 WeakSet

let obj = {id: 1}
let weakSet = new WeakSet([obj])
let weakMap = new WeakMap([[obj, 'hahaha']])

// 重写obj引用
obj = null

// {id: 1} 将在下一次 GC 中从内存中删除

如上所示,使用了 WeakMap 以及 WeakSet 即为弱引用,将 obj 引用置为 null 后,对象 {id: 1} 将在下一次 GC 中被清理出内存。

未清理的Console输出

写代码的过程中,肯定避免不了一些输出,在一些小团队中可能项目上线也不清理这些 console,殊不知这些 console 也是隐患,同时也是容易被忽略的,我们之所以在控制台能看到数据输出,是因为浏览器保存了我们输出对象的信息数据引用,也正是因此未清理的 console 如果输出了对象也会造成内存泄漏。

所以,开发环境下我们可以使用控制台输出来便于我们调试,但是在生产环境下,一定要及时清理掉输出。

可能有同学会觉得不可思议,甚至不相信,这里我们留一个例子,大家看完文章刚好可以自己测试一下(如何测试看完下文就明白啦!)

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

<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>

<body>
  <button id="click">click</button>

  <script>
    !function () {
      function Test() {
        this.init()
      }
      Test.prototype.init = function () {
        this.a = new Array(10000).fill('isboyjc')
        console.log(this)
      }

      document.querySelector('#click').onclick = function () {
        new Test();
      }
    }()
  </script>
</body>

</html>

内存泄漏排查、定位与修复

正如开头所说,程序运行一段时间后慢慢变卡甚至要崩溃了,不知道是什么原因,那我们就通过一个例子来走一遍排查、定位以及修复内存泄漏的整个流程,敲黑板,这是大家真正能够用上的。

既然上面我们说了几个会造成内存泄漏的案例,那我们就用这些案例写个 Demo 来从浏览器的角度反推排查是否存在内存泄漏,存在的话定位泄漏源并给予修复。

首先,我们来捏造一个内存泄漏例子:

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

<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>

<body>
  <button id="click">click</button>
  <h1 id="content"></h1>

  <script>
    let click = document.querySelector("#click");
    let content = document.querySelector("#content")
    let arr = []

    function closures() {
      let test = new Array(1000).fill('isboyjc')

      return function () {
        return test
      }
    }

    click.addEventListener("click", function () {
      arr.push(closures())
      arr.push(closures())

      content.innerHTML = arr.length
    });

  </script>
</body>

</html>

如上所示,这是一个非常简单的由不正当使用闭包构成的内存泄漏例子。

我们先来简单介绍下,只看 script 中的 JS 代码即可,首先,我们有一个 closures 函数,这是一个闭包函数,最简单的闭包函数想必不用向大家介绍了吧,然后我们为页面中的 button 元素绑定了一个点击事件,每次点击都将执行 2 次闭包函数并将其执行结果 push 到全局数组 arr 中,由于闭包函数执行结果也是一个函数并且存在对原闭包函数内部数组 test 的引用,所以 arr 数组中每一项元素都使得其引用的闭包内部 test 数组对象无法回收,arr 数组有多少元素,也就代表着我们存在多少次闭包引用,所以此程序点击次数越多,push 的越多,内存消耗越大,页面也会越来越卡。

那为了便于后期观察,程序中我们在每次点击按钮后,都把全局数组 arr 的长度数据更新到了页面上,即从 0 开始,每点击一次,页面数值加 2。

当然,这是我们自己写的例子,作为上帝的我们知道是什么原因导致的,那现在,忘掉这些,假设这是我们的一个项目程序,开发完成交付给测试,测试小姐姐发现在程序中不断点击按钮后页面越来越迟钝了,随即提了BUG。

作为程序员的我们肯定是:“刷新下页面不就好了,卡了就刷新刷新!!!”

嗯。。。产品和测试肯定都不会答应,一句用户至上就让我们改。。

行吧,那就改,首先第一步就要排查是哪里出了问题、是什么引起的,那此环节我们就叫排查问题阶段好了。

排查问题

Chrome 的开发者工具也就是我们所说的浏览器控制台(Chrome Devtool )功能其实十分强大,通过它可以帮助我们分析程序中像性能、安全、网络等各种东西,也可以让我们快速定位到问题源,只是大多数人并不熟悉其使用而已。

由于此文我们以内存泄漏为主,那我们就默认上述程序已经排查了除内存之外所有项且都没问题,接下来开始排查内存这块。

首先我们开启浏览器的无痕模式,接着打开要检查的网页程序代码,然后打开控制台,整个程序界面非常简单,如下图:

接着我们找到 Performance 这一面板,之前叫 Timeline ,它是 Chrome Devtool 用来监控性能指标的一个利器,可以记录并分析在网站的生命周期内所发生的各类事件,我们就可以通过它监控我们程序中的各种性能情况并分析,其中就包括内存,如下图:

接下来开始操作,在开始之前一定要确认勾选了 Memory 选项也就是上图标记 5 ,这样我们才可以看到内存相关的分析。

点击开始录制(标记 1)进入录制状态,随后先清理一下GC,也就是点击小垃圾桶(标记 6)。

接着疯狂点击页面中 click 按钮 100 次,这时页面上的数值应该是 200,我们再点击一下小垃圾桶,手动触发一次 GC。

再次疯狂点击页面中 click 按钮 100 次,这时页面上的数值应该是 400,然后停止录制。

我们来观察控制台生成的数据面板,如下图:

上面圈红的两块,也就是 Heap 对应的部分表示内存在周期性的回落,简单说就是我们的内存情况。

我们可以很明显的看到,内存数据呈现出一个不断上涨的趋势,可能有人会说这段时间内是不是还没执行 GC 呢?别急,还记得我们在 200 的时候点击了一下小垃圾桶吗,也就是我们中间手动触发垃圾回收一次,我们就可以通过上面的页面快照找出当页面值为 200 的那一刻在哪里,很简单,鼠标移到内存快照上找就行了,如下图:

可以看到,即使我们中间手动做了一次垃圾回收操作,但清理后的内存并没有减少很多,由此我们推断,此程序的点击操作可能存在内存泄漏。

OK,排查到问题了,那接下来就是定位泄漏源在哪了。

你可能会说,既然已经找到问题所在就是点击事件了,直接去改不就完了?

要知道,这是我们写的一个简单的例子,我们一下子就可以看出问题在哪,但是真实项目中一个点击事件里就可能存在大量操作,而我们只知道点击事件可能导致了内存泄漏,但不知道具体问题是在点击事件的哪一步骤上,更加细粒度的引起原因和位置我们也不知,这些都还需要我们进一步分析去定位。

分析定位

接下来我们开始分析定位泄漏源

Chrome Devtool 还为我们提供了 Memory 面板,它可以为我们提供更多详细信息,比如记录 JS CPU 执行时间细节、显示 JS 对象和相关的DOM节点的内存消耗、记录内存的分配细节等。

其中的 Heap Profiling 可以记录当前的堆内存 heap 的快照,并生成对象的描述文件,该描述文件给出了当下 JS 运行所用的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等,用它就可以定位出引起问题的具体原因以及位置。

注意,可不是 Performance 面板下那个 Memory ,而是与 Performance 面板同级的 Memory 面板,如下图:

现在页面值为 400,当然也可以刷新一下页面从 0 开始,这里我们选择继续操作

首先点击一下小垃圾桶(标记 3),触发一下 GC,把没用的东西从内存中干掉

点击开始生成快照(标记 1),生成第一次快照并选中,如下图:

简单介绍小上图大概表示的什么意思:

左侧列表中的 Snapshot 1 代表了我们生成的快照1,也就是刚刚那一刻的内存状态

选中 Snapshot 1 后就是右侧视图表格了,表格左上方有一个下拉框,它有四个值

  • Summary:按照构造函数进行分组,捕获对象和其使用内存的情况,可理解为一个内存摘要,用于跟踪定位DOM节点的内存泄漏
  • Comparison:对比某个操作前后的内存快照区别,分析操作前后内存释放情况等,便于确认内存是否存在泄漏及造成原因
  • Containment:探测堆的具体内容,提供一个视图来查看对象结构,有助分析对象引用情况,可分析闭包及更深层次的对象分析
  • Statistics:统计视图

该下拉默认会为我们选择 Summary ,所以下方表格展示的就是快照1中数据的内存摘要,简单理解就是快照1生成的那一刻,内存中都存了什么,包括占用内存的信息等等。

来简单了解下 Summary 选项数据表格的列都表示什么

  • Constructor:显示所有的构造函数,点击每一个构造函数可以查看由该构造函数创建的所有对象
  • Distance:显示通过最短的节点路径到根节点的距离,引用层级
  • Shallow Size:显示对象所占内存,不包含内部引用的其他对象所占的内存
  • Retained Size:显示对象所占的总内存,包含内部引用的其他对象所占的内存

OK,暂时知道这么多就可以了,我们继续操作,先点击小垃圾桶手动执行一次GC,然后点击 1 下页面的 click 按钮,最后再次点击生成快照按钮,生成我们的第二次快照。

为了准确无误,我们多来几次操作,如下:

先点击小垃圾桶手动执行一次 GC,然后点击 2 下页面的 click 按钮,最后再次点击生成快照按钮,生成我们的第三次快照

先点击小垃圾桶手动执行一次 GC,然后点击 3 下页面的 click 按钮,最后再次点击生成快照按钮,生成我们的第四次快照

随后,我们选中快照2,并将其上面的下拉框由默认的 Summary 选项切换为 comparison 选项,也就是对比当前快照与之前一次快照的内存区别,如下图:

我们再来看看选择 Comparison 下拉后,下方的表格列代表着什么,这里介绍几个重要的

  • New:新建了多少个对象
  • Deleted:回收了多少个对象
  • Delta:新建的对象数 减去 回收的对象数

诶,到这我们就有点那味儿了,我们需要重点关注 Delta ,只要它是正数就可能存在问题,贴心的控制台都已经给我们排好序了,最上面的几个我们依次看就可以。

当然,我们还需要知道这每一行的数据都代表的是什么,注意力转移到 Constructor 这一列,我们也说过,此列是构造函数,每一个构造函数点击都可以查看由该构造函数创建的所有对象,还是要先介绍下此列中常见的构造函数大致代表什么

  • system、system/Context 表示引擎自己创建的以及上下文创建的一些引用,这些不用太关注,不重要
  • closure 表示一些函数闭包中的对象引用
  • array、string、number、regexp 这一系列也能看出,就是引用了数组、字符串、数字或正则表达式的对象类型
  • HTMLDivElement、HTMLAnchorElement、DocumentFragment等等这些其实就是你的代码中对元素的引用或者指定的 DOM 对象引用

诶,又清晰了很多,那接下来我们就可以依次对比 1->2 / 2->3 / 3->4 来看到底哪里有问题了。

别着急,想一下现在的我们要怎么做?需要单独的点击一个快照再选中 comparison ,然后看 Delta 列为正数的项再进行分析,这样的操作需要进行 3 次,因为我们有 4 个快照,需要对比分析 3 次,甚至有时候可能生成的快照更多以此来确保准确性。

有没有更简单的方式呢?有的,我们可以直接选中要对比的快照,右侧表格上还有一个弹框我们可以直接选择快照进行对比,并且还会贴心的为我们过滤掉一些没用的信息:

我们来进行实际操作,左侧选中快照2,选择 快照1快照2 进行对比分析,结果如下:

可以看到,列表中只剩下对比过滤后的 4 项差异

system/Context 我们无需关心。

closure 上面也说过代表闭包引用,我们点击此项看一下具体的信息:

可以看到, closure 下有两个引用,还贴心的为我们指出了在代码的 21 行,点击选中其中一个引用,下方还有更详细的信息展示。

为什么展开后是两个引用?还记得我们在生成 快照2 时的操作吗,手动执行了一次 GC 并点击了一次 click 按钮,触发了一次点击事件,点击事件中我们执行并 push 了两次闭包函数,所以就是 2 条记录。

最后我们看 array ,这里存在数组的引用是完全因为我们案例代码中那个全局数组变量 arr 的存在,毕竟每次点击都 push 数据呢,这也是我们上面提到的为什么要额外关注全局变量的使用、要将它及时清理什么的,就是因为像这种情况你不清理的话这些全局变量在页面关闭前就一直在内存里,可能大家对构造函数列中有 2 项都是数组有疑问,其实没毛病,一项代表的是 arr 本身,一项代表的是闭包内部引用的数组变量 test (忘了的话回顾一下上面案例代码),这点也可以通过 Distance 列中表示的引用层级来 GET,一个层级是 7,一个层级是 8。至于数组引起泄漏的代码位置我们也可以点击展开并选中其引用条目,详情里就可以看到代码位置,同上面闭包一样的操作,这里就不演示了。

诶,那好像就知道具体的泄漏源了,我们再次证实一下,左侧选中快照4,选择 快照3快照4 进行对比分析,快照4 前我们做的操作是手动执行了一次 GC 并点击了三次 click 按钮,如果上述结论正确的话,应该同我们上面 快照1快照2 对比结果的数据项一致都是 4 项,但是每项的内部引用会是 6 条,因为这次快照前我们点击了三次按钮,每次执行并 push 了两次闭包函数,来看结果:

image-20210707041841176

嗯,到这里一切好像变得清晰明朗了,问题一共有 2 个,一是代码 21 行的闭包引用数组造成的内存泄漏,二是全局变量 arr 的元素不断增多造成的内存泄漏。

分析定位成功,进入下一步骤,修复并再次验证。

修复验证

由于这是临时写的一个案例,没有具体的场景,所以也就没有办法使用针对性的方式来修复,So,此步骤暂时忽略,不过在项目中我们还是要解决的。

比如全局对象一直增大这个问题,全局对象我们无法避免,但是可以限制一下全局对象的大小,根据场景可以超出就清理一部分。

比如闭包引用的问题,不让它引用,或者执行完置空,这都是上面说过的。

总之,一切都需要根据具体场景选择解决方案,解决之后重复上面排查流程看内存即可。

内存三大件

其实前端关于内存方面主要有三个问题,我把它们亲切的称作内存三大件:

内存泄漏 我们说很久了,对象已经不再使用但没有被回收,内存没有被释放,即内存泄漏,那想要避免就避免让无用数据还存在引用关系,也就是多注意我们上面说的常见的几种内存泄漏的情况。

内存膨胀 即在短时间内内存占用极速上升到达一个峰值,想要避免需要使用技术手段减少对内存的占用。

频繁 GC 同这个名字,就是 GC 执行的特别频繁,一般出现在频繁使用大的临时变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发 GC,频繁 GC 同样会导致页面卡顿,想要避免的话就不要搞太多的临时变量,因为临时变量不用了就会被回收,这和我们内存泄漏中说避免使用全局变量冲突,其实,只要把握好其中的度,不太过分就 OK。

最后

如果你的程序运行出现卡顿却找不到原因,那不妨试试本文的排查方法吧,说不定就是内存泄漏引起的,同时,它也是我们在做页面优化时需要额外注意的一个点。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/fS-PKDCM1apt5MhwlhoTjw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237270次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录