专栏首页OECOM深入理解js内存机制

深入理解js内存机制

js的内存机制在很多前端开发者看来并不是那么重要,但是如果你想深入学习js,并将它利用好,打造高质量高性能的前端应用,就必须要了解js的内存机制。对于内存机制理解了以后,一些基本的问题比如最基本的引用数据类型和引用传递到底是怎么回事儿?比如浅复制与深复制有什么不同?还有闭包,原型等等就迎刃而解了。

js类型

在js中,js的类型分为两个大类,分别是基本数据类型引用数据类型。我们暂时先抛开ES6不说,先只说在ES5中的类型。在ES5中有5中简单数据类型(也就是上面说的基本数据类型):Undefined、Null、Boolean、Number和String。还有1种复杂的数据类型————Object,Object本质上是由一组无序的名值对组成的。其中可以算在object中的还有Array和Function。

在内存当中,基本数据类型存放在栈中,引用数据类型存放在堆中。说到这里就要说一下内存空间了,一般来说,js的内存空间分为栈(stack)、堆(heap)、池(一般也会归类栈中)。其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池。

但是有一个变量是特殊的,那就是闭包中的变量,闭包中的变量并不保存中栈内存中,而是保存在堆内存中,这也就解释了函数之后之后为什么闭包还能引用到函数内的变量。

function A() {
  let a = 1
  function B() {
      console.log(a)
  }
  return B
}

闭包的简单定义是:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

函数 A 弹出调用栈后,函数 A 中的变量这时候是存储在堆上的,所以函数B依旧能引用到函数A中的变量。现在的 JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。

引用数据类型与堆内存

与其他语言不同,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。

为了更好的搞懂变量对象与堆内存,我们可以结合以下例子与图解进行理解.

var b = { m: 20 }; // 变量b存在栈中,对应的值就是一个索引指向对象{m: 20},{m:20}作为对象存在于堆内存中.

因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从变量对象中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

如此,就会出现我们经常被问到的关于引用数据类型的值问题了

var x =30;
var b = x;
x+=10;
console.log(b);

上面的问题我们很容易解答,答案就是会输出30,x的操作不会对b有什么影响,因为在变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。但是下面这个问题就有意思了

var x={m:1}
var y = x;
x.m++;
console.log(y.m);

通过输出我们发现答案是2。这是因为我们通过var y = x是执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在变量对象中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。

垃圾回收

在js中有垃圾回收机制,其作用是回收过期无效的变量,以防止内存泄漏。这些工作不需要我们去管理什么时候进行垃圾回收,js会自动进行,这让我们写起代码来感觉超级爽,哈哈。

下面来看一下js垃圾回收机制什么时候会回收变量。我们写代码的时候是区分全局变量和局部变量的,在此,我们看一下局部变量和全局变量的销毁。

  • 局部变量:局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。
  • 全局变量:全局变量什么时候需要自动释放内存空间则很难判断,所以在开发中尽量避免使用全局变量,以提高内存有效使用率

垃圾回收算法

现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。

引用计数

现代浏览器基本上已经不再使用了,在这里我们做一下简单的介绍。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。简单来说就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需要了。

// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
    age: 12,
    name: 'aaaa'
};
person.name = null; // 虽然name设置为null,但因为person对象还有指向name的引用,因此name不会回收
var p = person;
person = 1;         //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收
p = null;           //原person对象已经没有引用,很快会被回收

但是,如果出现了循环引用,那么这种方式就会存在一个大bug,看一下下面这个例子

function bigBug(){
    var objA = new Object();
    var objB = new Object();
    objA.bug1 = objB;
    objB.bug2 = objA;
}

在上面这个例子中,ab两个对象是互相引用的,也就是说他们的引用次数永远为2,如果不进行其他操作的话,这样的互相引用如果大量使用的话,就会造成内存泄漏问题。虽然说现在主流的浏览器都已经不在使用了,但是之前的IE版本还是那样,所以在写代码时我们应该尽量避免。避免的方法就是在不使用的时候进行手动解除循环绑定

objA.bug1 = null;
objB.bug2 = null;

标记清除

标记清除算法将“不再使用的对象”定义为“无法到达的对象”。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发无法触及到的对象被标记为不再使用,稍后进行回收。每一个变量都有自己的使用环境,当进入环境以后,垃圾回收机制就会给他打上一个“进入环境”的标签,从逻辑上来将,系统不能清除处于环境中的变量,因为只要是在环境中就有可能会使用到。当其离开环境时,会给其打上“离开环境”标签,这时候便可以进行回收了。

内存泄漏

内存泄漏可能对于前端开发着来说比较陌生,但是你肯定遇到过浏览器卡死的现象,卡死的原因就可能是因为一个死循环导致的内存爆满泄漏。所以对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。 对于不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak) 如果想模拟的话,可以按下面的步骤来进行操作:

  • 打开开发者工具,选择 Memory
  • 在右侧的Select profiling type字段里面勾选 timeline
  • 点击左上角的录制按钮。
  • 在页面上进行各种操作,模拟用户的使用情况。
  • 一段时间后,点击左上角的 stop 按钮,面板上就会显示这段时间的内存占用情况。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 利用jquery Rotare实现抽奖转盘

    相信大家对大转盘这个抽奖活动相当熟悉了吧,现在很多商家都会通过大转盘来进行商品的促销,有点事实体大转盘,有的是在网上进行的,有好多还是在微信平台进行大转盘抽奖活...

    无邪Z
  • Hibernate中的主要API

    Configuration类中提供了configure方法,可以用来读取指定的Hibernate属性文件,为获得数据库连接对象做好准则,代码为:

    无邪Z
  • MyEclipse2014激活工具

    本人一直以来编程都是用的eclipse,但是公司要求开发工具统一。无奈只得选择了MyEclipse。但是从网上下载下来后发现需要序列号激活才能永久使用,于是我就...

    无邪Z
  • Java lambda表达式

    Lambda表达式是 Java8 中最重要的新功能之一。使用 Lambda 表达式可以替代只有一个抽象函数的接口实现,告别匿名内部类,代码看 起来更简洁易懂。L...

    HUC思梦
  • Python Garbage Collection 与 Objective-C ARCPython GC 与 Objective-C ARC

    转载请注明出处 https://cloud.tencent.com/developer/user/1605429 Python GC 与 Objective-C...

    WWWWDotPNG
  • JVM垃圾回收算法

    java404
  • 小白的极简接口“自动化”

    接口可以说是大多数测试同学每天都接触的。在客户端、前端上,用Fiddler/Charles等工具查看接口、修改接口;在服务端,通过终端等工具查看接口触发的后端逻...

    用户5521279
  • 低代码RPA机器人开发平台或将引发新一轮IT服务升级

    据《2019中国企业数字化转型及数据应用调研报告》显示,超过80%的受调查企业其数据以非结构化为主,超过90%的企业内部存在数据孤岛,约80%的企业不认可自身数...

    蕉黄
  • 2020年Java基础高频面试题汇总(1.4W字详细解析)

    面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发

    程序员追风
  • 第6章—渲染web视图—使用Apache Tiles视图定义布局

    Tiles是一个免费的开源模板Java应用程序的框架。基于复合模式简化的用户界面的构建。对于复杂的网站仍是最简单、最优雅的方式与任何MVC技术一起工作。Stru...

    Dream城堡

扫码关注云+社区

领取腾讯云代金券