一、概念
由于 JavaScript 是运行在浏览器的脚本,主要用来操作 DOM,实现用户交互,所以 JavaScript 属于单线程,因为如果一个线程在删除一个 DOM 元素,另一个线程又要给这个 DOM 元素重绘,就会引发问题,浏览器不知道该执行那个线程,而且还会增加程序的设计复杂性。HTML5 虽然增加了 Web Worker 标准,允许 JavaScript 创建多个线程,但子线程完全受主线程控制,不能操作 DOM,所以本没有改变 JavaScript 单线程的本质,但是其他耗时较久的任务可以通过 Web Worker 完成。
那么问题来了,一些I/O操作,定时器任务,鼠标事件等异步任务都是怎么完成的昵?
二、JS 异步
JavaScript 所有的异步操作都是浏览器开启其他线程辅助完成的。
在 JavaScript 内部运行中,分为堆(heap)、栈(stack)和任务列队(task queue),堆主要是用来存储声明的变量和对象指针,栈中存储的是同步任务。异步任务会有相应的回调函数挂在任务列队中,任务本身是由浏览器的其他线程处理,等待处理完成后就会执行回调,这时候等待所有的同步任务执行完毕,再将任务列队的任务添加到栈中执行。而定时器原理是等待所有的同步任务执行完成后根据设置的时间插入到相应的任务列队中。
//1.定时器任务
setTimeout(function () {
console.log('a')
},0);
//2.异步获取接口数据
getData();
//3.耗时同步任务
for(let i=0;i<1000;i++){
if(i==999){
console.log('c')
}
}
function getData(){
const client = new XMLHttpRequest();
client.open('GET', url);
client.responseType = 'json';
client.send();
client.onreadystatechange = function () {
if (this.readyState !== 4) {
return;
}
if(this.status == 200){
console.log(this.response);
console.log('b');
}
};
}
//执行顺序 c a b
说明:
1.定时器任务:定时器任务会将所有同步任务执行完毕之后根据设置的时间添加到任务列队,也就是通常所说的定时器setTimeout(cb,2000);执行时间是“所有的同步任务执行完毕”+2秒;
2.异步获取接口数据任务:与定时器一样,也是等所有同步任务执行完毕之后,根据服务器返回接口数据的时间决定什么时候添加到任务列队(服务器什么时候返回,什么时候将回调添加到任务列队);
3.耗时同步任务:同步任务不管多少耗时,因为在主线程中,所以始终优先于异步任务执行。
三、图解