js事件循环

谈谈js的事件机制

单线程

js是单线程的,因为如果是多线程,一个线程添加dom,另一个删除dom,这样不就乱套了吗,所以js是单线程的
那么我们将其他线程,譬如ajax请求的线程、处理DOM时间的线程、定时器线程…etc等叫做工作线程

任务队列

由于js是单线程,所以每个任务都必须乖乖排好队,一个一个执行,这样如果一个任务执行的很慢,那么其他任务必须等!!

但是很多时候cpu是闲着的,因为IO设备很慢(Ajax操作从网络读取数据),这样就不得不等待结果了。那么js是如何解决的呢?

js的主线程不管IO设备,挂起处于等待中的任务,先运行后面的任务,等到IO设备返回了结果,在回头处理刚才挂起的任务

所以js中任务分为同步异步

异步任务是不进入主线程,而是进入任务队列的任务,只有任务队列通知主线程,某个异步可以执行了,该任务才会进入主线程执行

异步执行机制:

  1. 所有同步任务都在主线程上执行,形成一个技术栈
  2. 主线程之外,还有一个任务队列,只要异步任务有了运行结果,就在任务队列中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完,系统就读取任务队列,看里面有哪些事件,那些对应的异步任务,结束等待状态,进入执行栈,开始执行
  4. 主线程不断重复上面第三步

异步同步

同步:如果一个函数返回时,调用者能事先预见他的返回值,那就是同步的
异步:调用者事先不知道返回值,必须通过一些方法来获取,例如回调函数

异步过程构成要素

异步函数调用很快,不过后面还有诸如:工作线程执行异步任务、通知主线程、主线程调用回调函数等过程,暂称异步过程

主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。

异步函数有以下形式:

1
A(args...,callback)

异步过程包括两个要素:

  1. 发起函数(注册函数)A,发起异步请求
  2. 回调函数callback,处理异步结果
    上面均在主线程上调用,

事件和回调函数

任务队列是事件的队列,IO设备完成一项任务就在任务队列中加入一个事件,表示相关的异步任务可以进入执行栈了,主线程读取任务队列就是读取里面的事件

事件循环

任务队列中读取事件是循环不断的.
这里引用一张图(转引自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)。

主线程运行时,产生堆(heap)和栈(stack),栈中的代码调用各种外部的api,在任务队列中加入各种事件(click,load,done…etc),只要栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数

事件再webapi内执行,执行结束后将结果(回调函数)返回到任务队列,这时当执行栈里是空时,就进行事件循环,去消息队列里面取消息,进入执行栈,执行结束后又事件循环,继续从任务队列中取消息进入执行栈执行,不断循环

异步过程中,工作线程在异步操作完成后要通知主线程来执行回调函数,利用消息队列(先进先出)事件循环(主线程重复从消息队列中取消息、执行的过程)

工作线程将消息放到消息队列,主线程通过事件循环去读取消息

主线程就是不断从消息队列取消息、执行消息、再取消息、执行消息
简单来理解,消息队列里面存放我们异步任务的回调函数
相较于上面那个图,我们再来一个图,互相理解:

工作线程是生产者,主线程是消费者(只有一个消费者)。工作线程执行异步任务,执行完成后把对应的回调函数封装成一条消息放到消息队列中;主线程不断地从消息队列中取消息并执行,当消息队列空时主线程阻塞,直到消息队列再次非空。

定时器

setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。

上面的学习来自阮一峰老师segmentfault上的一篇文章,有兴趣的可以好好阅读
这里也有个视频可以看看,地址

文章目录
  1. 1. 单线程
  2. 2. 任务队列
  3. 3. 异步同步
  4. 4. 异步过程构成要素
  5. 5. 事件和回调函数
  6. 6. 事件循环
  7. 7. 定时器