单线程的javaScript
JavaScript从一诞生就是单线程的,未来也不会改变。虽然HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是新标准并没有改变JavaScript单线程的本质。
同步和异步
JavaScript中的任务可以分为两类:
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
- 异步任务:不进入主线程、而进入"任务队列"(详情看下文)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
一个异步过程包括两个要素:注册函数和回调函数,其中注册函数用来发起异步过程,回调函数用来处理结果。
setTimeout(fn, time);
上面代码中,setTimeout
就是注册函数,fn
是回调函数。
一般而言,异步任务有以下三种类型:
- 普通事件,如
click
。 - 资源加载,如
load
。 - 定时器,包括
setInterval
、setTimeout
等。
任务队列
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个“任务队列”,里面是各种需要当前程序处理的异步任务。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,“任务队列"上第一位的事件就自动进入主线程。但是,由于存在"定时器”,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
事件循环
下图中,主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API,异步操作执行完成后,就在任务队列中排队。只要栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些异步任务所对应的回调函数。
详细步骤如下:
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"。只要异步操作执行完成,就到任务队列中排队。
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到任务队列中排队。程序按照代码顺序执行完毕后,查询任务队列中是否有等待的异步任务。如果有,则按照次序从任务队列中取出放到执行栈中执行。执行完毕后,再从任务队列中获取异步任务,再执行,不断重复。
由于主线程不断的重复获得、执行异步任务;又由于“任务队列”是一个事件的队列,主线程读取“任务队列”就是读取里面有哪些事件,所以,这种机制被称为事件循环。
写在最后
本文参考文章如下:
JavaScript 运行机制详解:再谈Event Loop
深入理解javascript中的事件循环event-loop