js button onclick 传参_JS(Memory Management) 内存管理

a8bf4859ff12f5741bf54bd867555f02.png

转自:https://juejin.cn/post/6898301588356628488#stack

内存的生命周期

在JS中,无论新建一个变量,函数或者其他数据结构,JS引擎会默默的为我们做许多事-为对应的数据结构分配(allocate)内存空间并且在该数据不在被需要的时候回收(deallocate/release)它。

如果大家在对底层语言(C/C++)比较熟悉的话,在C++中利用标准库,allocate / deallocate 就可以实现内存的手动分配和回收

分配内存是预留内存的过程,而回收内存是将被占用的内存回收,为其他操作提供空间。

就像人有生老病死,内存也有对应的生命周期。每当赋值变量或者创建函数,内存都会经历如下的过程:7ace49f1f4f28c1ed41543bda99f649a.png

  • 分配内存JS引擎为我们分配我们需要操作对象的对应空间
  • 使用内存
    对内存的读写,只不过是对变量的读写
  • 回收内存JS引擎通过特定的算法判断内存是否需要释放,而被释放的内存用于为其他操作提供空间。

在内存管理器中的对象不仅仅包括JS对象,而且还囊括了函数和函数作用域。
JS中一切皆对象

堆和栈

我们从上文得知,JS引擎分配空间并在该空间不被引用的时候将其回收。

那这些被分配的空间具体被存放在哪里呢?

JS引擎有两个地方用于存放数据:堆(memory heap)和栈(stack)

是JS引擎存放数据的两个不同的数据结构。

栈:静态内存分配

1bc497576ac6870dcd06559d55d2c01a.png

上图,所有的值都被存放到 stack 中-->由于他们的类型都是原始类型(primitive)

stack 是JS用于存放静态数据的数据结构。静态数据是一种JS引擎在编译阶段能准确知道该数据大小的数据类型。在JS中,静态数据包括原始类型(string/number/boolean/undefined/null)和引用类型(指向对象和函数的指针)。

由于JS引擎知道它们的大小不会发生更改,所以每次都会为其分配指定大小的内存空间。

在代码执行之前分配内存的过程被称为 静态内存分配。

由于JS引擎为这些数据分配定值的内存空间,所以在stack存储的数据是有内存上限的。而该上限由不同浏览器各自决定。

堆:动态内存分配

heap 是JS用于存放对象和函数的地方。

不像stack,JS引擎不会为这些对象分配定值的内存空间。相反,这些空间是按需分配的。

该分配内存的方式被称为动态内存分配。

为了便于区分stack 和heap各自的区别,绘制如下的表格:

区别StackHeap
存储类型原始类型和引用类型对象和函数
内存是否定值在编译阶段已经确定内存大小在运行阶段确定大小
存储数据是否存在内存上限是(不同浏览器规则不同)不存在内存上限

示例

const person = {
  name: 'John',
  age: 24,
};

JS为这个对象在heap中分配空间。然而其属性的值为原始类型,所以属性值被存储在stack中。

const hobbies = ['hiking', 'reading'];

数组也属于对象,所以它也是被存储在heap中。

let name = 'John'; // 为string 分配内存
const age = 24; // 为数字分配内存

name = 'John Doe'; // 重新分配内存
const firstName = name.slice(0,4); // 重新分配内存

原始数据是不可变(immutable)的。为了将原来的值进行替换,JS会重新创建一个新的值。

JS中的引用

所有变量都在stack中存在指定的信息。在上文中我们得知,stack中存储两种类型的值- 原始类型和引用类型。原始类型我们不用过多解释,而引用类型需要着重解释一下。

由于对象和函数存放在heap中,而heap是一个杂乱无章的数据结构,没有指定的顺序,所以JS解释器在从上到下编译代码的时候,就会按照数据出现的顺序,依次按照数据类型存放到指定的位置。而在发现某个数据是非原始类型,就会在stack中存储其在heap中存储的引用地址。

cc1fa4967e50b6f7ce06cde5c1e193e6.png

垃圾回收

通过上文的学习,我们已经知道了JS引擎是如何存储不同的数据,但是凡事都是有头有尾的,既然存在内存的分配,那势必就会存在内存的回收。

和内存分配一样,内存回收的工作JS也为我们代劳了。并且还派专人(GC (garbage collector))全权负责此事。

一旦JS引擎发现变量或者函数不在被引用,GC将其占用的空间释放(deallocate/release)掉。

而回收内存最主要的问题是,内存是否被引用是一个不可预知的事,这也意味着没有一种算法能够在内存不被占用的时候,将所有的内存回收。

下面我们讨论一些比较典型的垃圾回收算法。

引用计数

这是一种最简单的方式。它通过回收那些没有指针指向的对象。6f7aa0189010dedbed6acd47a99871f7.png

通过上面的一系列操作,虽然将 person和 newPerson的引用都给置为 null,但是对象中 hobbies的还是被其他变量所引用。

循环引用

针对引用计数这种GC方式,有一种情况是无法处理的-循环引用。当一个或者多个对象互相引用,但是这些对象已经处于孤立状态。此时,引用计数就手足无措了。

let son = {
  name: 'John',
};

let dad = {
  name: 'Johnson',
}

son.dad = dad;
dad.son = son;

son = null;
dad = null;
537dac1a8faa031a9a7235045ad36fac.png

虽然在不使用son 和dad 的时候,将它们都置为null。这只是将它们与stack中的引用脱离的关系,但是在heap中,他们还彼此引用。

son 和dad 对象互相引用,而引用计数的算法在这两个对象没有被其他对象引用的时候是无法释放内存的。

标记清除

标记清除算法能够很好的解决循环引用的痛点。不同于引用计数通过计算对象引用个数的方式来决定是否进行垃圾回收,它采用了一种通过从根对象开始遍历,如果某个对象遍历不到,那就需要被GC释放对应的内存。

在浏览器中,这个根对象window对象,而在NodeJS中,是global对象。

69a9dd8503afaeb39bae9c41e7d3ac96.png

该算法通过 标记那些不能被访问到的对象,作为GC的目标。
根元素不参与标记过程!

内存泄漏

通过上文的介绍和对已有概念的掌握,我们来分析一些常规的可能造成内存泄漏的原因。

将变量挂载到全局变量

将数据肆无忌惮的存储在全局变量上,这是一种很常见的内存泄漏。

在浏览器环境中,如果在定义一个变量的时候,缺省了var/const/let,此时定义的变量就会被挂载到window对象上。

users = getUsers();

我们可以通过以strict mode来运行代码。

如果在某些情况下,逼不得已需要将变量挂载到全局变量上,你需要在该变量使命完成的时候,手动将其置为null

window.users = null;

关闭定时器和清除回调函数

忘记清除定时器和回调函数,将会使项目的代码越来越大。尤其针对SPA(单页面应用),在动态添加定时器和回调的时候要格外小心。

及时关闭定时器

const object = {};
const intervalId = setInterval(function() {
  // everything used in here can't be collected
  // until the interval is cleared
  doSomething(object);
}, 2000);

上面的代码,每2m执行一次,定时器中的变量在定时器没有关闭的时候,是一直无法被GC的。

所以,在不使用定时器的时候,需要将其销毁。

clearInterval(intervalId);

清除回调函数

const element = document.getElementById('button');
const onClick = () => alert('hi');

element.addEventListener('click', onClick);

element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);

清除DOM 引用

将DOM元素存储到JS中,可能也会导致内存泄漏。

当你想通过数组来销毁元素,这种情况是无法触发GC操作的

const elements = [];
const element = document.getElementById('button');
elements.push(element);

function removeAllElements() {
  elements.forEach((item, index) => {
    document.body.removeChild(document.getElementById(item.id));
    elements.splice(index, 1);
  });
}

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注我的博客 https://github.com/qappleh/Interview,让我们成为长期关系

  3. 关注公众号「深圳湾码农」,持续为你推送精选好文,回复「加群」加入面试互助交流群

点一下,代码无 Bug

f9d9f0a62ac60a5aa6ff3f5d96f3b4e4.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值