目录
前言
随着对js的理解逐渐加深,今天主要分享一下个人对内存的理解、风险和优化。
一、内存生命周期
- 分配内存:把变量、函数等分配到对应的内存空间
- 内存使用:使用变量、函数等,也就是读写内存
- 内存回收:回收变量、函数等
二、内存的分配和使用
1. 内存空间
在了解内存的分配前,需要先了解内存的空间结构。
js的内存空间分为堆与栈
- 栈内存:结构特点是后进先出
- 堆内存:结构类似于书架,通过key-value的形式读取
2. 变量的存放和使用
js中,变量分为了基本数据类型和引用数据类型
- 基本数据类型:保存在栈内存中,有固定的内存大小,通过值访问,主要有数字、字符串、布尔。
- 引用数据类型:保存在堆内存中,没有固定的内存大小,通过内存地址访问,主要有数组、对象、函数。
基本数据类型和引用数据类型的区别:
- 基本数据类型保存在栈内存中,引用数据类型保存在堆内存中。
- 变量保存的内容不同:基本数据类型在赋值时,是直接把数据值赋值给变量,而引用数据类型则是把在堆内存的地址赋值给变量,就比如你跟朋友分享某个网站一样,分享的只是一个连接,并不是里面的内容,所以,当一个变量改变了内存地址中的某一个值时,使用了同一个内存地址的变量中的某个值也会发生变化。
// 基本数据类型
// 基本数据类型在赋值时的步骤:
// 1. 在栈内存中开辟一个变量的空间
// 2. 把基本数据类型的值赋值个变量
const a = 123
// 把a的值赋值给b
let b = a
b = 444
console.log(a, b) // 123 444
const c = 123
// 基本数据类型之间比较的是值
console.log(a == c) // true
// 引用数据类型
// 引用数据类型在赋值时的步骤:
// 1. 在堆内存中开辟一个空间
// 2. 在空间中存放引用数据类型
// 3. 在栈内存中开辟一个空间存放变量
// 4. 把堆内存中的空间地址赋值给变量
const aa = {
name: '张三'
}
// 把c中保存的地址值赋值给d
const bb = aa
bb.name = '李四'
console.log(aa, bb) // {name: '李四'} {name: '李四'}
const cc = {
name: '李四'
}
// 引用数据类型之间比较的是内存地址值
console.log(bb == cc) // false
三、垃圾回收
垃圾回收分为:引用和标记两种机制
1、引用
引用的回收算法主要依赖于引用的概念,对象如果没有被引用,那么就回收,如果有引用,那么就不回收。
// 对象被变量o引用
let o = {
a: 1
}
// 清除对象的引用
o = null
缺陷:循环引用时就不会被回收,如下,对象o和对象o2都不会被回收
function fn() {
const o = {}
const o2 = {}
o.a = o2 // o中a属性引用o2
o2.a = o // o2中a属性引用o
}
fn()
2、标记
标记的回收算法主要是把对象引用到根对象的属性上,然后定期的使用跟对象上的属性,判断属性是否还存在对象的引用,如果没有就回收该对象,否则不回收,这样就解决了循环的问题。
缺陷:无法从跟对象查询到的对象都将被清除
// 未声明的变量会直接挂载到window上,也就是根对象
// 在没有清除对象的引用时,对象就不会被回收
function fn1() {
a = {}
}
fn1()
console.log(a) // {}
// 没有挂在到根对象时,就会被回收
function fn2() {
const aa = {}
}
fn2()
console.log(aa) // 报错
四、内存泄露
内存泄露就是变量、对象、函数等一直得不到回收,就会一直占用内存空间,达到一定数量时就会出现内存泄露。
有可能造成内存泄露方式:
-
未声明的变量或者意外的全局变量
-
DOM元素的不恰当处理:DOM元素被删除后,仍然有变量引用该DOM,导致无法被回收,需要将引用DOM的变量赋值为null
-
被遗忘的计时器或回调函数:定时器手动清除后还需要将变量赋值为null
-
闭包:内层函数会引用外层函数的变量,会导致无法回收,需要将对内层函数的引用设置为null
-
死循环
五、避免内存泄露
原则:不用的东西及时清除
- 减少不必要的全局变量,建议使用const、let声明变量
- 在使用完数据后及时解除引用
- 避免死循环