JS最初被设计用在浏览器中是单线程,因为如果浏览器中的JS是多线程的,会出现下面这个矛盾点:
那么现在有2个进程,process1 process2,由于是多进程的JS,所以他们对同一个dom,同时进行操作。 process1 删除了该dom,而process2 编辑了该dom,同时下达2个矛盾的命令,浏览器瞬间就感觉懵逼了。
对于单线程来说,代码的执行顺序为自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。所以js就引入了异步的概念。
在前面我介绍过一遍文章--javascript异步编程使用方法,现在要说的是js单线程是如何实现异步的。答案就是是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制。
先来看一下下面这段代码的执行顺序:
代码语言:javascript复制console.log(1)
setTimeout(function(){
console.log(2)
},0)
console.log(3)
实际的运行结果为1,3,2
通过上面的运行结果我们可以看出,setTimeout并没有立即执行,而是等到满足了一定条件才去执行的,这类代码,我们叫异步代码。
所以,这里我们首先知道了JS里的一种分类方式,就是将任务分为:同步任务和异步任务。在这里,同步任务为主线程。
于是乎就产生了一种执行机制:
首先判断代码是同步还是异步,如果是同步则进入主线程,如果是异步代码就进入event table;
异步任务在event table中注册函数,当异步代码达到执行条件时,就被推入到event queue事件队列当中;
同步任务进入主线程后会一直执行,直到同步任务执行完毕,主线程才会出现空闲,此时会去事件队列中查找是否有可执行的异步任务,如果有就推入到主线程中开始执行。
以上的三步基本上就构成了一个事件的循环机制--event loop。
当然这并不是一个完整的时间循环机制,其中的异步任务也有宏任务和微任务的区别。
总的来说宏任务一般包括:整体script代码块,setTimeout,setInterval
微任务包括:Promise,process.nextTick,ajax等。
首先判断代码是同步还是异步,如果是同步则进入主线程,如果是异步则看是宏任务还是微任务,如果是宏任务就放入到宏任务的队列当中,如果是微任务就放入到微任务的队列当中;
异步代码达到执行条件后就进入到事件队列当中,在此,微任务有一个优先权,就是当微任务中有任务,宏任务在事件队列当中的顺序就会靠后,即使宏任务达到了运行条件,也不会执行;
同步任务进入主线程后会一直执行,直到同步任务执行完毕,主线程出现空闲,此时去事件队列中查找,发现有宏任务和微任务,此时会等待微任务执行完成才会执行宏任务,然后按顺序执行完成。
这就是一个完整的时间循环机制。
看完上面的循环机制,再来看下面这这段代码:
代码语言:javascript复制setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i ){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
很明显,按照上面的说法,这里的执行结果应该是:马上执行for循环啦--代码执行结束---执行then函数啦--定时器开始啦
由此我们看出setTimeout并不是我们想的那样,我们认为设置时间为3000,就是3秒之后开始执行内部代码,但是这种说并不严谨,准确的解释是:3秒后,setTimeout里的函数被会推入event queue,而event queue(事件队列)里的任务,只有在主线程空闲时才会执行。如果主线程执行内容很多,执行时间超过3秒,比如执行了10秒,那么这个函数只能10秒后执行了。