栈存储
栈: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);不代表立即执行,指定某个任务在主线程最早可得的空闲时间执行,只要主线程执行栈内的同步任务全部完成,栈为空就马上执行。