js的单线程与异步

161 阅读3分钟

前言

说到js的单线程(single threaded)和异步(asynchronous),很多同学不禁会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。

js单线程

那么js单线程的具体操作流程是如何呢?
js将任务按顺序放在一个任务队列里面,js一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。

我们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是通过进栈和出栈实现的,比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一个人任务推入栈中(这个寻找的过程,叫做event loop,因为它总是循环的查找任务队列里是否还有任务)

js异步

前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。 也就是说js本身是单线程的,但是浏览器是多线程的,它可以为定时器,http请求,事件启用另外的线程。

那它是怎么和js回调函数结合使用形成异步呢

以ajax请求为例

  1. ajax请求内容的时候是异步的,当处于请求中时候的ajax,ajax在浏览器的一个线程中,而js线程还是在处理它任务队列里面的内容
  2. 当ajax请求完毕有内容返回后,要执行回调函数了,这个时候浏览器会把回调函数放置到js的任务队列中,等到js来执行函数。
  3. 这样就实现了异步,所以异步主要是浏览器帮忙实现的,而并不是js本身所有的属性

解释几个容易困惑的问题

function createW(){
	var ms = 10000+new Date().getTime();
	while(new Date() < ms) {};
	console.log("start");
}
function handleClick(){
	console.log("click");
}
setTimeout(handleClick,200);
createW();
console.log("finish");

//最后输出的结果就是 start finish click

这个结果的解释就是,放到任务队列的顺序是createW,console.log("finish"),200后才是handleClick函数,所以执行顺利也是如此,才会有这样的输出结果