在说 浏览器事件环 以前,先说几组概念:node
堆中存的是引用数据类型,是动态分配的内存,大小不定也不会自动释放。git
栈中存的是基本数据类型,会自动分配内存空间,自动释放;ajax
给张图片了解一下:数组
使用JS代码实现队列和栈的功能(就是用数组的增删方法):promise
let arr = new Array();
arr.push(1);
arr.push(2);
arr.shift();
复制代码
let arr = new Array();
arr.push(1);
arr.push(2);
arr.pop();
复制代码
首先,进程确定要比线程大,一个程序至少要有一个进程,一个进程至少要有一个线程。
下面看一张浏览器的工做机制:浏览器
因而可知,浏览器就是多进程的,当一个网页崩溃时不会影响其余网页的正常运行。每一个进程管理着浏览器不一样的部分,主要分为如下几种:bash
其中渲染引擎内部有三个线程是咱们着重须要关注的网络
其中js线程和ui线程是互斥的,数据结构
当js执行的时候可能ui还在渲染,那么这时ui线程会把更改放到队列中 当js线程空闲下来 ui线程再继续渲染并发
除此以外还有一些其它的线程,这也是咱们分发异步任务时用到的线程
说一个老生常谈的问题:
JS是单线程的,任务是须要一个一个按顺序执行的,可是说的是 JS的主线程是单线程 的,他能够建立子线程,来帮他完成任务。
同步和异步关注的是消息通知机制
任务队列:
同步任务 是指在主线程上执行的任务,只有前一个任务执行完毕,下一个任务才能执行。
异步任务 是指不进入主线程,而是进入任务队列(task queue)的任务,只有主线程任务执行完毕,任务队列的任务才会进入主线程执行。
实现过程:
1.全部同步任务都在主线程上执行,造成一个执行栈;
2.只要异步任务有了运行结果,就在任务队列(task queue)(队列是一个先进先出的数据结构,而栈是一个先进后出的数据结构)之中放置一个事件;
3.一旦执行栈中的全部同步任务执行完毕,系统就会读取任务队列,又将队列中的事件放到stack中依次执行,就是执行异步任务中的回调函数。这个过程是循环不断的,这就是Event Loop(事件循环);
关于线程进程,同步异步,想了解更多请参考个人文章《进程与线程、同步与异步、阻塞与非阻塞、并发与并行》
在上面的异步任务中又分为两种:宏任务 和 微任务
常见的宏任务和微任务:
macro-task
(宏任务,优先级低,先定义的先执行): ajax,setTimeout, setInterval, setImmediate, I/O,事件,postMessage,MessageChannel(用于消息通信)
micro-task
(微任务,优先级高,而且能够插队,不是先定义先执行):process.nextTick, 原生 Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver
Promise自己是同步的,Promise.then是异步的
宏任务和微任务的区别:微任务是会被加入本轮循环的,而宏任务都是在次轮循环中被执行。简单就是说,微任务会比宏任务提早执行
简单的说就是:由于微任务的优先级较高,因此会先将微任务的异步任务取出来进行执行,当微任务的任务都执行完毕以后,会将宏任务中的任务取出来执行。
本轮循环是指什么呢?JS主线程会从任务队列中提取任务到执行栈中执行,每一次执行均可能会再产生一个新的任务,对于这些任务来讲此次执行到下一次从任务队列中提取新的任务到执行栈以前就是这些新生任务的本轮。
给出一张网上很火的一张图:
从上图看出:
1.主线程运行的时候产生堆(heap)和栈(stack)
2.栈中的代码调用各类外部API,它们在"任务队列"中加入各类事件(例如:click,load,done)
3.只要栈中的代码执行完毕,主线程就会去读取"任务队列",将队列中的事件放到执行栈中依次执行。
4.主线程继续执行,当再调用外部API时又加入到任务队列中,等主线程执行完毕又会接着将任务队列中的事件放到主线程中。
5.上面整个过程是循环不断的。
例题(执行顺序):
JS代码本质上仍是从上往下执行的
//例题1
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 --> promise1 --> promise2 --> setTimeout
1.先执行同步任务,输出script star
script end
2.而后执行异步任务,先执行异步任务的微任务,输出 promise1
3.接着返回了一个Promise
,而后又.then
,仍是微任务,接着执行,输出 promise2
4.最后执行异步任务中的宏任务,输出 setTimeout
//例题2
console.log(1);
setTimeout(function(){
console.log(2);
new Promise(function(resolve,reject){
console.log(3);
resolve();
}).then(res=>{
console.log(4);
})
});
setTimeout(function(){
console.log(5);
})
console.log(6);
复制代码
输出结果顺序:1 6 2 3 4 5
1.执行栈中同步任务先执行,先走console.log(1)
和console.log(6)
;
2.接着是遇到setTimeout
将它们的回调函数放入MacroTask
(宏任务队列);
3.而后将任务队列中的回调函数依次放入主执行栈中执行,console.log(2)
,接着console.log(3)
;是当即执行,console.log(4)
;是微任务放入MicroTask
中先执行;
4.最后执行第二个setTimeout
的回调函数console.log(5)
;
//例题3
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(data => {
console.log('then3');
});
},1000);
Promise.resolve().then(data => {
console.log('then1');
});
Promise.resolve().then(data => {
console.log('then2');
setTimeout(() => {
console.log('setTimeout2');
},1000);
});
console.log(2);
复制代码
输出结果顺序:2 then1 then2 setTimeout1 then3 setTimeout2
1.先执行栈中的内容,也就是同步代码,因此2
被输出出来;
2.而后清空微任务,因此依次输出的是 then1
then2
;
3.因代码是从上到下执行的,因此1s后 setTimeout1
被执行输出;
4.接着再次清空微任务,then3
被输出;
5.最后执行输出setTimeout2
下面例题就不一一分析了,能够本身尝试运行并分析一下
例题4
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(6);
});
}, 0);
Promise.resolve(3).then((data) => {
console.log(data);
return data + 1;
}).then((data) => {
console.log(data)
setTimeout(() => {
console.log(data + 1)
return data + 1;
}, 1000)
}).then((data) => {
console.log(data);
});
复制代码
输出结果顺序:1 3 4 undefined 2 6 5
//例题5
setTimeout(() => {
console.log('A');
}, 0);
var obj = {
func: function () {
setTimeout(function () {
console.log('B')
}, 0);
return new Promise(function (resolve) {
console.log('C');
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
复制代码
输出结果顺序:C E D A B
这里内容不作过多解释
1. setTimeout,setImmediate谁先谁后?
setImmediate
的回调永远先执行。2. nextTick和promise.then谁快?
3. nextTick和其它的定时器嵌套
setImmediate(function(){
console.log(1);
process.nextTick(function(){
console.log(4);
})
})
process.nextTick(function(){
console.log(2);
setImmediate(function(){
console.log(3);
})
})
2 1 3 4
复制代码
缘由在于nextTic
k在node
中的执行实际和浏览器中不彻底同样,虽然它们在第一次进入事件环时都会先执行,但若是后续还有nextTick
加入,node
中只会在阶段转换时才会去执行,而浏览器中则是一有nextTick
加入就会当即执行。
形成这样区别的缘由在于,node
中的事件环是有6种状态的,每种状态都是一个callbcak queue
,只有当一个状态的callback queue
中存放的回调都清空后才会执行nextTick
。
4. 定时器指定的回调函数必定会在指定的时间内执行吗?
不必定,先不说node中事件环的六种状态之间转化时的猫腻,光是浏览器中的事件环也可能由于本轮循环的执行时间过长,长的比定时器指定的事件还长从而致使定时器的回调触发被延误。
node11.x版本以后,node事件环就慢慢和浏览器的事件环同样了,这里就先不做过多解释