浅谈对JS的异步和单线程的理解

单线程:

首先,JS是单线程的语言,顾名思义,整个的任务都在一条线上,一个任务执行完了,下一个任务才能执行。所以对于JS来说,它自己是不可能是异步的。

异步:

但是JS的宿主环境,比如浏览器是多线程的,浏览器会通过事件驱动的方式,让JS能够进行异步执行,从而达到单线程进行异步执行的效果。

为什么JS要做到异步执行?

因为js里面的网络请求,定时器事件以及事件监听等事件,会消耗大量的时间,如果JS进行单线程执行,如果这些操作一直在浪费时间,就会导致整个页面无法往下执行,页面假死等情况,所以针对此类情况,浏览器为这些耗时较长时间的任务另外开辟了新的线程,所以这些任务都是异步的。

JS主线程如何知道异步任务要进行执行或者是执行完成要进入到队列中呢?

这也就是我们上文说的事件驱动的方式,

setTimeout(function(){
    console.log('come');
},50);

 

执行这段代码的时候,浏览器执行异步操作,等到50毫秒之后,会触发事件驱动(定时器事件),定时器里面的回调函数会进入到JS主线程的任务队列中。所以说JS是单线程的,浏览器让JS实现了异步。

主线程:

如图

浅谈对JS的异步和单线程的理解

解析:如图所示,我们把之前所说的浏览器开辟的新的执行线程为webAPIs,任务队列也就是callback queue,JS的主线程也就是由heap(堆)和stack(栈)共同组成,函数的执行也就是通过进进栈和出栈,所以stack里面的函数是执行完,然后出栈。如果发现stack为空了,则说明主线程里面的任务执行完了,则去callback queue(任务队列)中查找下一个任务,并将其推入stack(栈)中,这个寻找的过程在图中表示为event loop(因为它总是循环的查找任务队列里是否还有任务)。

 

由此图以及解释我们可以完全理解之前的疑问点:

1.这段代码的打印顺序

setTimeout(function(){
    console.log(2);
},0);
console.log(1);

 

结果为:1  2 

理由:因为在执行setTimeout的时候,会把他的回调函数放到任务队列中,但是在stack中发现还有console.log(1)还没执行,得先执行了,然后出栈,然后stack(栈)为空了,则将setTimeout的回调函数推入stack(栈)中进行执行。故执行结果为 1 2

2.Ajax请求是否异步

由之前内容可得,ajax请求是异步的,在执行的时候,浏览器会开辟一个新的线程进行请求,然后请求完成之后,他的回调函数会进入 callback queue,然后就是等到主线程的stack为空的时候,将此回调函数推入主线程的stack(栈)中,执行,出栈。

3.界面渲染线程是单独开辟的线程,是不是DOM一变化,界面就立刻重新渲染?

浏览器的机制规定:界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态。

如何利用浏览器的异步机制?

其实直白点的方法就是

function f1(callback){
    callback();
}
f1(f2);

假设f2函数需要借助f1函数的某些结果之后才能执行f2函数,也就是在f1的末尾处去调用f2函数,达到了异步的目的。