内存,事件循化机制
垃圾回收
引用计数
这个算法主要是定义"对象有没有被其他对象引用"为依据,来决定是否回收。
但是如果两个对象之前互相引用,那么就会回收不了
标记清除
这个算法是定义"对象能否被获得"为依据,能否被获得指的是从根元素,也可以理解为全局,去获得被引用的元素,垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
现在所有浏览器的垃圾回收机制都是基于标记清除的算法来实现优化。
Event Loop
JavaScript 的并发模型基于“事件循环”
首先要有队列queue,栈stack,堆heap的概念
一个 web worker 或者一个跨域的
iframe
都有自己的栈,堆和消息队列。两个不同的运行时只能通过postMessage
方法进行通信。如果另一运行时侦听message
事件,则此方法会向其添加消息。
事件循环就是会不断判断队列中是否有可执行的事件,来运行。
有了这个概念之后,开始思考,运行一段js代码的时候,事件循环机制是如何工作的。
众所周知,js是单线程作业,这里在浏览器环境中要明白浏览器是多进程的,js脚本执行是浏览器渲染进程中的一个线程。和它同为线程的还有GUI渲染线程,定时器触发线程等
分析下面这段代码
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
函数声明两个函数,函数声明提升。bar函数返回了foo函数
最后一行执行bar方法
函数调用形成一个栈帧
此时的消息队列中,是没有任何事件的,因为js引擎并没有异步操作或者注册了什么事件(即没有向消息队列中添加消息)。只有上面的同步任务
所以此时的执行栈里面是bar函数,bar函数上面又压了个foo函数,当foo函数运行结束之后,这个函数(参数和局部变量:指向堆的一系列指针)就会被抛出栈,然后等bar运行完毕,同样也会被抛出栈,当栈为空时,意味着同步任务的一个函数执行完了,事件循环机制就去拉取消息队列中的第一个任务执行。
现在有下面这段代码
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');
先说执行结果
这涉及到宏任务(macrotask)和微任务(microtask)
setTimeout 众所周知是定时器,上面代码的意思是立即插入一个匿名回调函数,
promise 众所周知是异步处理函数,后面接了两个异步回调
回到我们的队列,栈,堆中来
js执行,此时我们第一个遇到的是同步代码( script start),然后遇到setTimeout,
什么时候将setTimeout的回调推入消息队列呢?计时器线程会计时,等倒计时结束,事件触发线程就会负责推入,这里为0,表示以最快的速度推入,就是代码运行到这里就推入了。但是推入之后不一定立马执行,因为此时我们的执行栈不为空,
然后继续执行同步任务。遇到promise,也会向消息队列中添加任务。但是和setTimeout不同的是,promise添加的是微任务,所以会有两个队列来进行分工,macrotask在一个队列中,microtask在另一个队列中。
所有会有下面的
那么这两个队列的执行顺序是怎样的?
当执行栈(starck)为空时就会将microtask里面的任务都执行,那么相信结果就很明显了
执行完 start 和end ,这时strack里面是空的,那么就执行第一个promise回调,执行完第一个promise回调,遇到了第二个promise回调,那就会将第二个执行回调放到microtask里面,执行完之后,栈为空,那就再去运行微任务队列,输出promise2,这时microtask里面是没有任务了,那将由macrotask接手接下来的操作,执行setTimeout
的回调,输出setTimeout
那么哪些是macrotask,哪些是microtask呢