2024年最新【知识点】JS中的内存管理,前端ui基础教程

基础面试题

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

主要内容包括:HTML,CSS,JavaScript,浏览器,性能优化等等

var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1; // 现在,“这个对象”的原始引用o被o2替换了

var oa = o2.a; // 引用“这个对象”的a属性

// 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = “yo”; // 最初的对象现在已经是零引用了

// 他可以被垃圾回收了

// 然而它的属性a的对象还在被oa引用,所以还不能回收

oa = null; // a属性的那个对象现在也是零引用了

// 它可以被垃圾回收了

由上面可以看出,引用计数算法是个简单有效的算法。但它却存在一个致命的问题:循环引用。

如果两个对象相互引用,尽管他们已不再使用,垃圾回收不会进行回收,导致内存泄露。

来看一个循环引用的例子:

function f(){

var o = {};

var o2 = {};

o.a = o2; // o 引用 o2

o2.a = o; // o2 引用 o 这里

return “azerty”;

}

f();

上面我们申明了一个函数 f ,其中包含两个相互引用的对象。 在调用函数结束后,对象 o1 和 o2 实际上已离开函数范围,因此不再需要了。 但根据引用计数的原则,他们之间的相互引用依然存在,因此这部分内存不会被回收,内存泄露不可避免了。

再来看一个实际的例子:

var div = document.createElement(“div”);

div.onclick = function() {

console.log(“click”);

};

上面这种JS写法再普通不过了,创建一个DOM元素并绑定一个点击事件。 此时变量 div 有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。 一个循序引用出现了,按上面所讲的算法,该部分内存无可避免的泄露了。

为了解决循环引用造成的问题,现代浏览器通过使用标记清除算法来实现垃圾回收。

标记清除算法

标记清除算法将“不再使用的对象”定义为“无法达到的对象”。 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。 但反之未必成立。

工作流程:

  1. 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记。

  2. 从根部出发将能触及到的对象的标记清除。

  3. 那些还存在标记的变量被视为准备删除的变量。

  4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。

循环引用不再是问题了

再看之前循环引用的例子:

function f(){

var o = {};

var o2 = {};

o.a = o2; // o 引用 o2

o2.a = o; // o2 引用 o

return “azerty”;

}

f();

函数调用返回之后,两个循环引用的对象在垃圾收集时从全局对象出发无法再获取他们的引用。 因此,他们将会被垃圾回收器回收。

内存泄漏


什么是内存泄漏

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。

对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。 否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

本质上讲,内存泄漏就是由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。

内存泄漏的识别方法

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。 这就要求实时查看内存的占用情况。

在 Chrome 浏览器中,我们可以这样查看内存占用情况

  1. 打开开发者工具,选择 Performance 面板

  2. 在顶部勾选 Memory

  3. 点击左上角的 record 按钮

  4. 在页面上进行各种操作,模拟用户的使用情况

  5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况

  6. 来看一张效果图:

我们有两种方式来判定当前是否有内存泄漏:

  1. 多次快照后,比较每次快照中内存的占用情况,如果呈上升趋势,那么可以认为存在内存泄漏

  2. 某次快照后,看当前内存占用的趋势图,如果走势不平稳,呈上升趋势,那么可以认为存在内存泄漏

在服务器环境中使用 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字段为准。

常见的内存泄露案例


意外的全局变量

function foo() {

bar1 = ‘some text’; // 没有声明变量 实际上是全局变量 => window.bar1

this.bar2 = ‘some text’ // 全局变量 => window.bar2

}

foo();

在这个例子中,意外的创建了两个全局变量 bar1 和 bar2

被遗忘的定时器和回调函数

在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数。 要记得回收这些回调函数。举一个 setInterval的例子:

var serverData = loadData();

setInterval(function() {

var renderer = document.getElementById(‘renderer’);

if(renderer) {

renderer.innerHTML = JSON.stringify(serverData);

}

}, 5000); // 每 5 秒调用一次

如果后续 renderer 元素被移除,整个定时器实际上没有任何作用。 但如果你没有回收定时器,整个定时器依然有效, 不但定时器无法被内存回收, 定时器函数中的依赖也无法回收。在这个案例中的 serverData 也无法被回收。

闭包

在 JS 开发中,我们会经常用到闭包,一个内部函数,有权访问包含其的外部函数中的变量。 下面这种情况下,闭包也会造成内存泄露:

var theThing = null;

var replaceThing = function () {

var originalThing = theThing;

var unused = function () {

if (originalThing) // 对于 'originalThing’的引用

console.log(“hi”);

};

theThing = {

longStr: new Array(1000000).join(‘*’),

someMethod: function () {

console.log(“message”);

}

};

};

setInterval(replaceThing, 1000);

这段代码,每次调用 replaceThing 时,theThing 获得了包含一个巨大的数组和一个对于新闭包 someMethod 的对象。 同时 unused 是一个引用了 originalThing 的闭包。

这个范例的关键在于,闭包之间是共享作用域的,尽管 unused 可能一直没有被调用,但是 someMethod 可能会被调用,就会导致无法对其内存进行回收。 当这段代码被反复执行时,内存会持续增长。

DOM 引用


很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中。

var elements = {

image: document.getElementById(‘image’)

};

function doStuff() {

elements.image.src = ‘http://example.com/image_name.png’;

}

最后

你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

我特地针对初学者整理一套前端学习资料

前端路线图

vue.js的36个技巧

Stuff() {

elements.image.src = ‘http://example.com/image_name.png’;

}

最后

你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

我特地针对初学者整理一套前端学习资料

[外链图片转存中…(img-HCSJvPYN-1715073189462)]

[外链图片转存中…(img-aZzh4BTU-1715073189462)]

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值