JS 单线程与异步
- JS 是单线程语言(可以提高效率,作为浏览器脚本语言,JS 主要用途就是操作 DOM,与用户互动。所以它只能是单线程,否则会造成执行混乱)。例如,有要删除节点的函数,有要操作节点的函数,万一多线程执行顺序乱了就坏了。
- 浏览器只分配一个主线程(JS 引擎线程,在 JS 引擎中负责解释和执行JavaScript代码的唯一线程)来执行任务(函数),而且一次只能执行一个任务,这些任务必须形成一个任务队列排队等候执行。但前端某些任务非常耗时,比如网络请求、定时器和事件监听,如果让它们和别的任务一样,都老老实实排队等待执行的话,执行效率会非常低,甚至导致页面假死。所以,浏览器为这些耗时任务开辟了另外的线程(我们暂且称之为工作线程),主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
- JS 是单线程语言,但 JS 的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动)使得 JS 具备了异步的属性。
JS 异步实现机制可以理解为,当主线程的异步函数在被调用的时候,会请求工作线程的帮助。工作线程接收这个任务并执行,主线程可以继续运行后面的函数,而不必阻塞在这。
任务队列(task queue)和事件循环(event loop)
JS 的任务可以分为两类
- 同步任务: 需要执行的任务在主线程上排队,依次执行。只有前一个任务执行完毕,才能执行后一个任务
- 异步任务: 不进入主线程而进入”任务队列”(task queue,也叫事件队列或者消息队列)的任务,只有等主线程任务执行完毕,”任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行
异步运行机制具体如下
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)
- 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件
- 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- 主线程从”任务队列”中读取事件,这个过程是循环不断的,形成event loop(事件循环)
只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。
JS 引擎存在 monitoring process 进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 那里检查是否有等待被调用的函数。
任务队列
“任务队列”是一个事件队列(也可以理解成消息队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。
“任务队列”中的事件,除了IO设备的事件以外,还包括一些用户操作的事件(比如鼠标点击、页面滚动等等),比如 $(selectot).click(function),这些都是相对耗时的操作。只要指定过这些事件的回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。
所谓”回调函数”(callback),就是那些会被主线程挂起来的代码,前面说的点击事件 $(selectot).click(function) 中的 function 就是一个回调函数。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。例如 ajax 的 success,complete,error 也都指定了各自的回调函数,这些函数就会加入“任务队列”中,等待执行。
异步操作的执行
JS引擎线程。主线程。负责解析 Javascript 脚本,运行代码。一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页( renderer 进程)中无论什么时候都只有一个JS线程在运行JS程序。
JS 异步操作是由浏览器内核的 webcore 来执行的, webcore 包含三种 webAPI ,分别是 DOM Binding、network、timer 模块。
- DOM Binding 模块处理一些 DOM 绑定事件,如 onclick 事件触发时,回调函数会立即被 webcore 添加到任务队列中(事件触发线程)
- network 模块处理 Ajax 请求,在网络请求返回时,才会将对应的回调函数添加到任务队列中(异步 http 请求线程)
- timer 模块会对 setTimeout 等计时器进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中(定时器触发线程)
例如任务队列里放的是 ajax 这类的任务,会交给浏览器发起 HTTP 请求去执行的,当有了返回结果就会在任务队列中增加一个事件,表示该 ajax 请求已经返回了结果,任务队列里的任务和 JS 主线程是同时执行的。不影响 JS 是单线程的这个结论,只能说浏览器还会提供工作线程来供 JS 异步执行。