前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

JavaScript进阶_常见内存泄露及如何避免|博客系列

# 前言

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

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

  • 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 树中,另一个在字典中。那么将来需要把两个引用都清除。

比如下面这个例子:

// 在对象中引用DOM
var 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);
    };
}
@前端进阶之旅: 代码已经复制到剪贴板

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

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

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浏览器的控制台Performance或Memory
  • Node提供的process.memoryUsage方法

# Chrome浏览器的控制台Performance或Memory

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

  1. 在网页上右键, 点击“检查”打开控制台(Mac快捷键option+command+i);
  2. 选择Performance面板(很多教材中用的是Timeline面板, 不知道是不是版本的原因);
  3. 勾选Memory, 然后点击左上角的小黑点Record开始录制;
  4. 点击弹窗中的Stop结束录制, 面板上就会显示这段时间的内存占用情况。

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

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

# 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进阶_内存机制 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合