JS运行机制
单线程
JavaScript是一门单线程的语言,这就意味着一个时间节点,Js只能处理一个任务。这是由于JS作为客户端脚本语言,它主要作用就是与用户进行交互,操作Dom节点。
试想如果多线程情况下,两个线程对同一个dom同时进行增上改查的操作,这时候的浏览器就不知道以哪一个线程为准了。
因此,为了语言的简单,JS被设计成了一门单线程的语言。即:JS在运行过程中,同一时间只能处理一个任务。
由于JavaScript是单线程的语言,它总是在一件任务完成以后才会进行下一件任务,因此如果当某一个任务十分耗的时候,例如进行一次网络请求,如果服务器不能马上响应,那么后面的任务都会处于阻塞的状态。这样的事情是绝对不允许发生的,因此JavaScript中通过同步任务、异步任务、任务队列和事件循环机制(Event Loop)去处理这个问题。
同步任务
同步任务是指直接在主线程中执行的任务。同步任务在主线程中按照代码的顺序,排队依此被主线程执行。
异步任务
异步任务是指当主线程执行到异步任务的代码时,它并不会立即执行异步任务,而是将异步任务放入回调队列(CallBack Queue)中,当回调队列中的事件被触发了,这时候异步任务会被放入任务队列中,当主线程中的同步任务都被执行完毕后,此时会从任务队列中按照先进先出的顺序,将任务队列中的异步任务依此取出放入主线程执行异步任务的代码。
任务队列
任务队列中依此放置所有的被触发的异步任务,主线程会在清空所有任务后,不段检查任务队列是不是为空,如果不为空会按照先进先出的规则,不断取出异步任务放入主进程的任务栈中执行。
案例理解
<script>
console.log("Start"); //同步任务
setTimeout(function(){
console.log("Task1");//异步任务
},1000);
setTimeout(function(){
console.log("Task2"); //异步任务
},0);
console.log('End'); //同步任务
</script>
这一段代依此输出的结果是:
Start
End
Task2
Task1
别废话,show me animation!
这个动画很好的展示了控制台输出的结果的过程。我们来一步步看代码是如何执行的:
1、代码执行到console.log("Start");由于是同步任务,直接执行,在控制台输出Start
2、代码执行到Task1的setTimeout(),由于是异步任务,放入回调列队
3、代码执行到Task2的setTimeout(),由于是异步任务,放入回调列队
4、代码执行到console.log('End');由于是同步任务,直接执行,在控制台输出End
5、主线程的任务栈已经没有任务,主线程的任务栈试图不断从任务队列中读取任务,由于回调队列中的Task2的setTimeout()第二个延迟执行参数设置的是0毫秒(根据HTML5规范,设置4毫秒以下,默认设置为4毫秒),所以4毫秒后被放入了任务队列。主线程发现任务队列有任务,则把异步任务放入主线程的任务栈,进行执行,因此此时输出Task2
6、当Task1的setTimeout()设置的1000毫秒到后,Task1进入任务队列,这时候主线程的任务栈又处于空的状态,发现任务队列有任务,则将任务队列中的Task1放入任务栈中,进行执行,因此此时输出Task1
上面的整个流程就是事件循环机制的流程,总结一句话就是:主进程的任务栈在执行完所有同步任务后,会不断从任务队列中取出已经被触发的异步任务,进行执行。
哪些语句会被放入异步队列
1、setTimeOut和setInterval
2、DOM事件,例如:onClick、onChange等。
3、ES6中的Promise
什么时候被放入任务队列
在上面的动画中我们发现异步任务并不是马上被放入任务队列中的,而是事件被触发时,才会放入任务队列中。
例如:
setTimeout()当延迟的时间到达后,才会将回调函数放入任务队列,等待主线程的任务栈去取出异步的任务;
DOM事件中例如click事件,代码执行到addEventListener()它会把这个异步任务放入回调队列,当用户点击DOM,触发事件时,才会把异步任务放入任务队列中;
复盘
Javascript是一个单线程语言,但它通过利用异步任务和事件循环机制来避免执行耗时的任务,例如:网络任务等,阻塞线程。
在时间循环过程中,我们应该关注到,异步时间并不是马上被放入任务队列中,而是当异步任务被触发后,才会将异步任务放入任务队列中,等待主线程的任务栈来按照先进先出的规则,取出并执行任务。
我是大麦,如果喜欢我的文章,请给我一颗小心心。