单线程
js是单线程的,因为如果是多线程,一个线程添加dom,另一个删除dom,这样不就乱套了吗,所以js是单线程的
那么我们将其他线程,譬如ajax请求的线程、处理DOM时间的线程、定时器线程…etc等叫做工作线程
任务队列
由于js是单线程,所以每个任务都必须乖乖排好队,一个一个执行,这样如果一个任务执行的很慢,那么其他任务必须等!!
但是很多时候cpu是闲着的,因为IO设备很慢
(Ajax操作从网络读取数据),这样就不得不等待结果了。那么js是如何解决的呢?
js的主线程不管IO设备,挂起处于等待中的任务,先运行后面的任务,等到IO设备返回了结果,在回头处理刚才挂起的任务
所以js中任务分为同步
和异步
异步任务是不进入主线程,而是进入任务队列
的任务,只有任务队列
通知主线程,某个异步可以执行了,该任务才会进入主线程执行
异步执行机制:
- 所有同步任务都在主线程上执行,形成一个技术栈
- 主线程之外,还有一个
任务队列
,只要异步任务有了运行结果,就在任务队列
中放置一个事件 - 一旦
执行栈
中的所有同步任务执行完,系统就读取任务队列
,看里面有哪些事件,那些对应的异步任务,结束等待状态,进入执行栈,开始执行 - 主线程不断重复上面第三步
异步同步
同步:如果一个函数返回时,调用者能事先预见他的返回值,那就是同步的
异步:调用者事先不知道返回值,必须通过一些方法来获取,例如回调函数
异步过程构成要素
异步函数
调用很快,不过后面还有诸如:工作线程执行异步任务、通知主线程、主线程调用回调函数等过程,暂称异步过程
主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。
异步函数有以下形式:1
A(args...,callback)
异步过程包括两个要素:
- 发起函数(注册函数)
A
,发起异步请求 - 回调函数
callback
,处理异步结果
上面均在主线程上调用,
事件和回调函数
任务队列
是事件的队列,IO设备完成一项任务就在任务队列
中加入一个事件,表示相关的异步任务可以进入执行栈了,主线程读取任务队列就是读取里面的事件
事件循环
在任务队列
中读取事件是循环不断的.
这里引用一张图(转引自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)。
主线程运行时,产生堆(heap)和栈(stack),栈中的代码调用各种外部的api,在任务队列
中加入各种事件(click,load,done…etc),只要栈中的代码执行完毕,主线程就会去读取任务队列
,依次执行那些事件所对应的回调函数
事件再webapi内执行,执行结束后将结果(回调函数)返回到任务队列,这时当执行栈里是空时,就进行事件循环,去消息队列里面取消息,进入执行栈,执行结束后又事件循环,继续从任务队列中取消息进入执行栈执行,不断循环
异步过程中,工作线程在异步操作完成后要通知主线程来执行回调函数,利用消息队列(先进先出)
和事件循环(主线程重复从消息队列中取消息、执行的过程)
工作线程将消息放到消息队列,主线程通过事件循环去读取消息
主线程就是不断从消息队列取消息、执行消息、再取消息、执行消息
简单来理解,消息队列里面存放我们异步任务的回调函数
相较于上面那个图,我们再来一个图,互相理解:
工作线程是生产者,主线程是消费者(只有一个消费者)。工作线程执行异步任务,执行完成后把对应的回调函数封装成一条消息放到消息队列中;主线程不断地从消息队列中取消息并执行,当消息队列空时主线程阻塞,直到消息队列再次非空。
定时器
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部
添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。
上面的学习来自阮一峰老师和segmentfault上的一篇文章,有兴趣的可以好好阅读
这里也有个视频可以看看,地址