首先我们要知道浏览器是由什么组成的
1.js主线程
1.1.heap(堆)
- 堆:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。特点先进后出(数组的shift方法)
let arr = [];
arr.push(1);
arr.push(2);
console.log(arr.shift()); // 1
1.2.stack(栈)执行上下文
- 栈:会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间 特点先进后出(函数执行的销毁过程)
- 当前栈也叫执行上下文(执行同步任务)
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
alert("不关闭后面是不会执行的");
console.log(1);
2.WebAPIs (DOM,ajax,setTimeout)
3.callback queue(回调/任务 队列)
3.1任务队列的特点:先进先出,先放进去的异步任务先执行
setTimeout(()=>{
console.log(1);
},300)
setTimeout(()=>{
console.log(3);
},200)
setTimeout(()=>{
console.log(2);
},200)
- 输出的结果是 3 2 1
3.2任务队列里面存放的是异步任务(宏任务,微任务)
异步任务:不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
for(var i = 0; i < 5; i++){
setTimeout(() =>console.log(i), 0);
}
console.log("ok");
// 先输出ok,再输出5个5
3.1.1异步任务的分类
- 1.macro-task(宏任务): setTimeout, setInterval, setImmediate(ie), I/O MessageChannel
- 2.micro-task(微任务): process.nextTick, 原生Promise,Object.observe(已废弃),MutationObserver(h5的一个方法,在dom更新完之后执行)
3.1.2异步任务的执行顺序
微任务的执行顺序在宏任务之前
setTimeout(()=>{
console.log("setTimeout1");
},0)
Promise.resolve().then(()=>{
console.log("Promise");
})
process.nextTick(()=>{
console.log("nextTick");
})
// 输出 nextTick Promise setTimeout1
通过上面的了解我们大致知道浏览器是先执行同步任务,执行完之后再执行异步任务。
回到主题,我们来看看浏览器是怎么实现一个事件环(for)的
-
1.所有同步任务都在主线程上执行,形成一个执行栈
-
2.主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件
-
3.一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
-
4.主线程从任务队列中读取事件,这个过程是循环不断的
整个的这种运行机制又称为Event Loop(事件循环)
看下面一段代码
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(()=>{
console.log('then1');
});
},0)
Promise.resolve().then(()=>{
console.log('then2');
Promise.resolve().then(()=>{
console.log('then3');
});
setTimeout(function(){
console.log('setTimeout2');
},0)
})
// 输出结果 then2 then3 setTimeout1 then1 setTimeout2
我们得出一个结果
浏览器的事件环和任务队列里面微任务和宏任务的关系
- 先执行栈中的内容 执行后 清空微任务
- 取一个宏任务 再去清空微任务 ,再去取宏任务,一直到任务队列中没有为止
最后再说一下,node的事件环和任务队列里面微任务和宏任务的关系
- 本质上和浏览器的事件环不变就是队列中微任务和宏任务的执行顺序有些差别
- 先执行栈中的内容 执行后 清空微任务
- 清空宏任务 再去清空微任务 ,再去清空宏任务,一直到任务队列中没有为止