JavaScript进阶(高频面试题)

Stella981
• 阅读 470

前言

这一章节给大家介绍的知识点相对比较简单, 但是却是非常重要的. 而且也是在面试过程中经常会被问到的一部分内容.

通过此次阅读你可以学习到:

  • 4种常见的内存泄露

  • 内存泄露的识别方法

4种常见的内存泄露

其实在实际开发中, 我们很容易不经意的就写出内存泄露的代码, 比如以下几种情况可能都是你遇到过的.

一、意外的全局变量

未声明的变量

当我们在一个函数中给一个变量赋值但是却没有声明它时:

function fn () {    a = "Actually, I'm a global variable"}

此时变量a相当于是window对象下的一个变量:

function fn () {    window.a = "Actually, I'm a global variable"}

而之前我们已经说了全局变量是很难被垃圾回收器回收的, 所以要是有这种意外的全局变量应该要避免.

使用this创建的变量

还有一种情况是这样的:

function fn () {    this.a = "Actually, I'm a global variable"}fn();

我们知道, 这里this的指向是window, 因此此时创建的a变量也会被挂载到window对象下.

避免此情况的解决办法是在 JavaScript 文件头部或者函数的顶部加上 'use strict', 开启严格模式, 使得this的指向为undefined, 这样就可以避免了.

当然如果你必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。

二、被遗忘的计时器或回调函数

定时器引起

当我们在代码中使用定时器也有可能会造成内存泄露:

var serverData = loadData()setInterval(function() {    var renderer = document.getElementById('renderer')    if(renderer) {        renderer.innerHTML = JSON.stringify(serverData)    }}, 5000)

上面的例子🌰中, 节点renderer引用了serverData.在节点renderer或者数据不再需要时,定时器依旧指向这些数据。所以哪怕当renderer节点被移除后,interval 仍旧存活并且垃圾回收器没办法回收,它的依赖也没办法被回收,除非终止定时器。

对象观察者

还有一个就是关于观察者模式的案例:

var btn = document.getElementById('btn');function onClick (element) {    element.innerHTMl = "I'm innerHTML"}btn.addEventListener('click', onClick);

对于上面观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。因为老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。

但是,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经可以正确检测和处理循环引用了。即回收节点内存时,不必非要调用 removeEventListener 了。

三、脱离DOM的引用

这种造成内存泄露的原因简单来说就是:

如果把DOM 存成字典(JSON 键值对)或者数组,此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。那么将来需要把两个引用都清除。

比如下面这个例子:

// 在对象中引用DOMvar elements = {    btn: document.getElementById('btn')}function doSomeThing () {    elements.btn.click();}function removeBtn () {    // 将body中的btn移除, 也就是移除 DOM树中的btn    document.body.removeChild(document.getElementById('button'));    // 但是此时全局变量elements还是保留了对btn的引用, btn还是存在于内存中,不能被GC回收}

上面👆这种情况, 可以手动将引用给清除: elements.btn = null.

四、闭包

还有一种情况就是我们之前提到过的闭包. 也就是局部变量销毁时, 闭包的这种情况.

首先让我们明确一点:闭包的关键就是匿名函数能够访问父级作用域中的变量.

来看一个简单的例子🌰:

function fn () {    var a = "I'm a";    return function () {        console.log(a);    };}

因为变量afn()函数内的匿名函数所引用, 因此这种变量是不会被回收的.

那就有人问了, 即使这样会造成什么不妥吗? 在上面👆这个案例中当然看不出有什么. 若是将情况变得复杂一些呢?

var globalVar = null; // 全局变量var fn = function () {    var originVal = globalVar; // 局部变量    var unused = function () { // 未使用的函数        if (originVal) {            console.log('call')        }    }    globalVar = {        longStr: new Array(1000000).join('*'),        someThing: function () {            console.log('someThing')        }    }}setInterval(fn, 100);

先请你花上一分钟看看上面的案例, 你会发现:

  1. 每次调用 fn函数的时候都会产生一个新的对象 originVal;

  2. 变量 unused是一个引用了 originVal的闭包;

  3. unused虽然未被使用, 但是它引用的 originVal迫使它留在内存中, 并不会被回收.

解决办法是: 可以在fn的最底部, 将originVal设置成null.

内存泄露的识别方法

上面👆介绍了这么多种可能会造成内存泄露的情况, 那么有没有什么实际的办法让我们看到内存泄露的表现呢?

当然是有的. 现在常用的是以下2种方式:

  • Chrome浏览器的控制台 PerformanceMemory

  • Node提供的 process.memoryUsage方法

Chrome浏览器的控制台PerformanceMemory

Chrome 浏览器查看内存占用,按照以下步骤操作。

  1. 在网页上右键, 点击“检查”打开控制台( Mac快捷键 option+command+i);

  2. 选择 Performance面板(很多教材中用的是 Timeline面板, 不知道是不是版本的原因);

  3. 勾选 Memory, 然后点击左上角的小黑点 Record开始录制;

  4. 点击弹窗中的 Stop结束录制, 面板上就会显示这段时间的内存占用情况。

JavaScript进阶(高频面试题)

JavaScript进阶(高频面试题)

如果内存的使用情况一直在做增量, 那么就是内存泄露了:

JavaScript进阶(高频面试题)

或者你可以使用我在《记录一次定时器及闭包问题造成的内存泄漏》中的方法进行检查.

Node提供的process.memoryUsage方法

另一个就是Node提供的process.memoryUsage方法, 这一块我用的不是很多, 这里就贴上教材:

console.log(process.memoryUsage());// { rss: 27709440,//  heapTotal: 5685248,//  heapUsed: 3449392,//  external: 8772 }

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下:

rss(resident set size):所有内存占用,包括指令区和堆栈。heapTotal:"堆"占用的内存,包括用到的和没用到的。heapUsed:用到的堆的部分。external:V8 引擎内部的 C++ 对象占用的内存。

判断内存泄露, 是看heapUsed字段.

总结

总的来说, 常见的内存泄露包括:

  • 意外的全局变量

  • 被遗忘的定时器或回调函数

  • 脱离DOM的引用

  • 闭包中重复创建的变量

如何避免内存泄露:

  • 注意程序逻辑,避免“死循环”之类的

  • 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收

  • 避免创建过多的对象 原则:不用了的东西要及时归还

后记

以上就是胡哥今天给大家分享的内容,喜欢的小伙伴记得收藏转发,点击在看推荐给更多的小伙伴。

胡哥有话说,一个有技术,有情怀的胡哥!现任京东前端攻城狮一枚。 胡哥有话说,专注于大前端技术领域,分享前端系统架构,框架实现原理,最新最高效的技术实践!

关注胡哥有话说,学习前端不迷路,欢迎多多留言交流...JavaScript进阶(高频面试题)

本文分享自微信公众号 - 胡哥有话说(hugeyouhuashuo)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
浪人 浪人
3年前
Android 内存泄露:详解 Handler 内存泄露的原因与解决方案
前言在Android开发中,内存泄露十分常见1.内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中2.内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。这就导致了内存泄漏。本文将详细讲解内存泄露的其中一种情况:在Handler中发生的内
记住几种出现内存泄漏的点
Android内存优化——常见内存泄露及优化方案如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄露。在Android开发中,一些不好的编程习惯会导致我们的开发的app存在内存泄露的情况。下面介绍一些在Android开发中常见的内存泄
Wesley13 Wesley13
3年前
go中内存泄露的发现与排查
一,什么是内存泄漏Go中的并发性是以goroutine(独立活动)和channel(用于通信)的形式实现的。处理goroutine时,程序员需要小心翼翼地避免泄露。如果最终永远堵塞在I/O上(例如channel通信),或者陷入死循环,那么goroutine会发生泄露。即使是阻塞的goroutine,也会消耗资源
Stella981 Stella981
3年前
Android内存溢出分析
   内存溢出,是Android开发中常遇到的问题,解决起来总是摸不着头脑。今天爬爬就来讲讲如何定位内存溢出。OOM(内存溢出)和MemoryLeak(内存泄露)有什么关系?OOM可能是因为MemoryLeak,也可能是你的应用本身就比较耗内存(比如图片浏览型的,或者应用本身的
Stella981 Stella981
3年前
Android内存泄露
一、app内存泄露调试1、通过adbshelldumpsysmeminfo packageName来查看内存使用状况在没有打开应用的情况下,该命令返回的数据是这样的:!(https://oscimg.oschina.net/oscnet/df96ad0b2daa0ab54fb479bc0d2ad985fa9.png)2、
Wesley13 Wesley13
3年前
Java内存溢出和内存泄露后怎么解决
1.首先这里先说一下内存溢出和内存泄露的区别:内存溢出outofmemory,是指程序在申请内存时,没有足够的内存空间供其使用,出现outofmemory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存泄露memoryleak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,
Stella981 Stella981
3年前
Netty如何监控内存泄露
Netty如何监控内存泄露\TOC\前言一般而言,在Netty程序中都会采用池化的ByteBuf,也就是PooledByteBuf以提高程序性能。但是PooledByteBuf需要在使用完毕后手工释放,否则就会因为PooledByteBuf申请的内存空间没有归还进而造成内存泄露,最终OOM。而一旦
Easter79 Easter79
3年前
ThreadLocal 内存泄露的实例分析
前言之前写了一篇深入分析ThreadLocal内存泄漏问题(https://my.oschina.net/thinwonton/blog/1505136)是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例。分析问题的过程比结果更重要,理论结合实际才能彻底分析出内存泄漏的原因。案例与分析
Stella981 Stella981
3年前
Javascript内存泄露
1.什么是内存泄露?内存泄露是指分配给应用的内存不能被重新分配,即使在内存已经不被使用的时候。正常情况下,垃圾回收器在DOM元素和event处理器不被引用或访问的时候回收它们。但是,IE的早些版本(IE7和之前)中内存泄露是很容易出现的,因为内存管理器不能正确理解Javascript生命周期而且在周期被打破(可以通过赋值为null实现)前不会回收
Easter79 Easter79
3年前
ThreadLocal的内存泄露的原因分析以及如何避免
前言在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮性。内存泄露内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果