js的内存机制-栈,堆

394 阅读4分钟

栈存储

栈:6种基本数据类型,string,number,null, undefined, bool, symbol

特点:按值访问
存贮的值的大小固定
由系统自动分配内存空间
空间小,运行效率高
先进后出,后进先出 栈中的DOM,ajax,setTimeout会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行。

const a = 20;
 let b = a;
 b = 30;
 console.log(a, b); // 20, 30
 //栈存储,栈里存了个a 20,将a的值赋值给b,此时栈里存了a 20, b 20,然后改变b的值为30,此时栈里存的是a 20,b 30

堆存储

堆:引用数据类型-object,arr
特点:存储引用数据类型 按引用访问 存储的值大小不定,可动态调整 主要用来存放对象 空间大,但是运行效率相对较低 无序存储,可根据引用直接获取

const m = {a: 10, b: 20};
const n = m; 
n.a = 20;console.log(m,n) // {a: 20, b: 20,} {a: 20, b: 20}

eg:const obj = {a: 1, b: 2}; 
堆内存中存储的是{a: 1, b: 2};而栈内存中存储obj,栈内存中变量名obj对应的值指向与堆内存中的{a: 1, b: 2}

js不允许直接操作堆内存中的值,所以当我们要访问堆内存中的数据的时候,我们访问的是存储在栈内存中的指针,然后再从堆内存中获取数据

const obj = {
      arr: [2,3,5,6],
    };
let c = obj.arr;
const d = {arr: obj.arr,};c.splice(0, 1);
console.log(c, d); // [3,5,6] [3,5,6] obj.arr存储在堆中,无论赋值多少次,改变的都是指针的引用,所以d的值相应的也被改变
// d值不被改变,就需要转换一下
const obj = {
      arr: [2,3,5,6],
    };
let c = obj.arr; const d = {arr: [...obj.arr],};c.splice(0, 1);
console.log(c, d) // [3,5,6], [2,3,5,6]
const obj = {
      arr: [2,3,5,6],
    };
let c = obj.arr; const d = {arr: JSON.parse(JSON.stringify(obj.arr)),};c.splice(0, 1);
console.log(c, d) // [3,5,6], [2,3,5,6]

看下图加深理解

看一下下面的几个例子,打印出来的是什么?

eg1:
console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

首先任务进入执行栈,先执行打印出script start script end ,然后setTimeout是一个宏任务,会被放进队列里,Promise是一个微任务,也会被放进队列里,当主线程内的任务执行完毕为空,会将队列中的放进去,然后遵循这先进后出,后进先出的原则,先打印出promise1 promise2 所以结果为: script start script end promise1 promise2 setTimeout

eg2:
console.log(1)

setTimeout(function(){
    console.log(4);
})
let promise2 = new Promise(function(resolve,reject){
    console.log(3)
    resolve(100)
}).then(function(data){
    console.log(100)
})
console.log(2)
// 1 3 2 100 4
setTimeout是宏任务,而Promise.then是微任务 这里的new Promise()是同步的,所以是立即执行的。
  • 任务进入执行栈-》判断是同步任务还是异步任务-》
  • 同步-》进入主线程-》任务全部执行完毕-》读取任务队列中的结果,进入主线程执行
  • 异步-》进入事件表-》注册回调函数-》进入事件队列-》读取任务队列中的结果,进入主线程执行
  • 意思:同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
结论:栈里的代码永远先执行,然后再从队列中加入事件,依次执行

微任务与宏任务

微任务:原生promise

宏任务 setTimeout,script,setInterval,setImmediate

微任务和宏任务都属于队列,不属于栈中,先执行宏任务,将宏任务放入事件表eventqueue中,然后执行微任务,将微任务放入eventqueue中,当往外面拿的时候先从微任务里拿这个回调函数,然后再从宏任务的queue上面拿宏任务的回调函数(先进后出)

  • setTimeout这个函数,是经过指定时间后,把要执行的任务加入到事件队列中,又因为是单线程任务需要一个一个执行,如果前面的任务需要的时间太久,那只能等,导致真正的延迟时间远远大于自己设定的
  • setTimeout(Fn, 0);不代表立即执行,指定某个任务在主线程最早可得的空闲时间执行,只要主线程执行栈内的同步任务全部完成,栈为空就马上执行。