js难点汇总01_内存

正所谓底层基础决定上层建筑,如果想写出优雅高性能的前端应用,就必须了解JS的内存机制。他会帮助你理解譬如:闭包、深浅复制、引用数据类型和引用传递;

堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,并且可以自由扩展,如:数组可以无限扩充,对象可以自由添加属性。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。相对于简单数据类型而言,简单数据类型就比较稳定,并且它只占据很小的内存。不将简单数据类型放在堆是因为通过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。所以简单数据类型的值直接存放在栈中。

记住一句话:能量是守衡的,无非是时间换空间,空间换时间的问题

JS内存周期

内存分配、内存使用、内存回收

JS类型

谈到js内存,我们从数据类型来作为切入口帮助理解,js数据类型分两大类,分别是基本数据类型引用数据类型,基本数据类型又包含:null、undefined、boolean、number、string,引用数据类型是复合数据类型:Object、Function、Array等

JS内存模型

JS内存空间分为栈(stack)、堆(heap)、池(一般放在栈中),栈存放变量(基本数据类型),堆存放复杂对象(医用数据类型),池存放常量

1. 基础数据类型与栈内存

JS中的基础数据类型都有固定的大小,往往都放在栈内存中,由系统自动分配内存空间;可以直接操作栈内存空间的值,遵循先进后出的原则,如下图所示

2. 引用数据类型与堆内存

引用数据类型例如Object、array大小都是不固定的,是放在堆内存中的变量;JavaScript不允许直接访问堆里面的变量,所以不能直接操作堆内存空间,在操作对象中,我们操作的是对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的,这里的引用可以理解为报错在变量对象中的一个地址,这个地址和堆内存的实际值是相关联的,这就是我们常说的指针也就是是我们去复制一个对象的时候只是复制了一个指针,或者说复制了一个引用地址,如下图所示:

这就衍生了如何去复制一个对象,一般两种方法,一种是转化为string(就是把引用类型转化为基本数据类型,这样就可以直接复制值了),第二种是Object.assign(),通过Object.assign({},srcObj);得到的新对象为‘深拷贝’;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。这是Object.assign()特别值得注意的地方。 

function deepClone2(obj) {
  let _obj = JSON.stringify(obj),
  return JSON.parse(_obj);
}
let srcObj = {
  'name': 'lilei',
  'age': '20'
};

let copyObj2 = Object.assign({}, srcObj, {
  'age': '21'
});

3. 内存空间管理

由于JavaScript有自动垃圾回收机制,所以很多人都不太在意内存使用问题,内存的分配和回收全都交给了自动管理;但是我们要做到知其然,知其所以然,这样才能写出高性能的优雅代码;

JavaScript的内存周期补充:

1. 分配所需要的内存 

2. 使用分配的内存(读写)

3. 不需要的时候释放、回收

let demo=20;//分配内存
alert(demo+20);//使用内存
let demo=null;//使用完毕,释放内存

补充第三部回收,JavaScript有自动垃圾收集机制,那么这个自动垃圾收集机制的原理是什么呢?其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。

在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。

补充:闭包中的变量存储于堆内存还是栈内存的问题,请先看如下代码快:

function demo(){
    let a=1;
    function children(){
        console.log(a)
    }
    return children
}

我先阐述下闭包定义:闭包就是能够读取其他函数内部变量的函数

代码块里:函数demo返回了一个函数children,并且函数children使用了外部函数demo的变量a,函数children就被称为闭包;

我们来从堆栈来解释以上代码块:函数demo中的变量是存储在堆内存的,所以函数children可以访问函数demo中的变量

4. 内存泄漏

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

如果想模拟的话,可以按下面的步骤来进行操作:

1. 打开开发者工具,选择 Memory

2. 在右侧的Select profiling type字段里面勾选 timeline

3. 点击左上角的录制按钮。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章