prefacio
Como todos sabemos, JavaScript
es de un solo subproceso, pero inevitablemente, JavaScript
también se requieren algunas tareas asincrónicas, como el siguiente ejemplo.
function foo() {
console.log("first");
setTimeout(( function(){
console.log( 'second' );
}),5);
}
for (var i = 0; i < 1000000; i++) {
foo();
}
复制代码
En el ejemplo anterior, los resultados de la ejecución se mostrarán primero first
y luego todos los resultados second
, en lugar de una ejecución alternativa.
En este proceso, es obvio que se han producido tareas asincrónicas concurrentes, por lo que la pregunta JavaScript
es, ¿cómo logra un único subproceso asincrónico?
JavaScript
¿Por qué es de un solo hilo?
Como Java
programador, después de saber que JavaScript
es de un solo subproceso, la primera reacción es preguntarse por qué un lenguaje está diseñado para ser de un solo subproceso ¿Por qué no puede usar subprocesos múltiples para mejorar la eficiencia?
JavaScript
es de un solo subproceso, en relación con su propósito. Como lenguaje de scripting del navegador, JavaScript
el uso principal es para interactuar con el usuario, así como para operar DOM
. Esto determina que solo puede ser un único hilo, de lo contrario traerá problemas complejos de sincronización. Por ejemplo, suponga JavaScript
que hay dos subprocesos al mismo tiempo, un subproceso DOM
agrega contenido a un nodo y el otro subproceso elimina este nodo, ¿qué subproceso debe tomar el navegador?
Entonces, para evitar la complejidad, desde el principio, JavaScript
es de un solo subproceso, lo que se ha convertido en una característica central de este lenguaje y no cambiará en el futuro.
JavaScript
¿Cómo lograr la asincronía?
Ahora que hemos entendido JavaScript
por qué es de un solo subproceso, ¿cómo logra la capacidad asíncrona?
JavaScript
La capacidad asíncrona la proporciona principalmente el entorno de ejecución .
JavaScript
entorno operativo
JavaScript Runtime
Ahí es donde se JavaScript
ejecuta el código. Por ejemplo , se puedeJavaScript
ejecutar en , también se puede ejecutar en , y amboschrome
node
chrome
node
JavaScript Runtime
由上图可知,JavaScript Runtime
主要包括Js Engine
与WebAPI
等内容
Js Engine
将我们编写的 JavaScript
转换为更高效的机器码,以实现更好的性能。
chrome
浏览器中的 JavaScript
由 V8
引擎处理。V8
引擎主要包括内存堆与执行栈两个部分
- 内存堆:用于分配
JavaScript
程序使用的内存。 - 执行栈:在执行栈中,您的
JS
代码被读取并逐行执行。
除了引擎,JavaScript Runtime
也提供了WebAPI
供JS
代码调用,WebAPI
提供了网络请求,定时器,事件监听等多种能力
因为JS Runtime
并不是单线程的,而是持有一个线程池,因此WebAPI
中的代码是运行在其他线程的,自然也就提供了异步的能力
事件循环机制
JS
分为同步任务和异步任务,同步任务都在主线程上执行,形成一个执行栈- 栈中的代码调用
WebAPI
时也就异步任务,异步任务执行完成后,它们会在事件队列中添加各种事件 - 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
- 执行栈与任务队列如此循环,也就是事件循环机制
需要注意的是,一旦执行栈中的所有同步任务执行完毕(此时JS
引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中
因此setTimeout
设置的时间并不是准确的,可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码,因此存在误差。
总结
JavaScript
本质上是运行在浏览器里的脚本语言,为了简单与避免操作DOM
时引入同步问题,所以JavaScript
被设计成了单线程的语言。
JavaScript
的异步能力是由运行环境提供的,通过WebAPI
与事件循环机制,单线程的JS
也可以执行异步任务。
参考资料
JavaScript 运行机制详解:再谈Event Loop
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
Javascript — single threaded, non-blocking, asynchronous, concurrent language