Cómo comenzar rápidamente con VUE3.0: aprenda
sobre el contexto de ejecución, la pila de ejecución y el mecanismo de ejecución (tareas sincrónicas, tareas asincrónicas, microtareas, macrotareas y bucles de eventos) en js
Es un punto de prueba frecuente en las entrevistas. Algunos amigos pueden sentirse confundidos cuando se les pregunta, así que lo resumiré hoy, esperando que pueda serles útil frente a la pantalla.
Antes de hablar sobre el contexto de ejecución y el mecanismo de ejecución js
js
hablemos de subprocesos y procesos.
En términos oficiales,线程
es la unidad más pequeña de programación CPU
.
? En términos oficiales,进程
es la unidad más pequeña de asignación de recursos CPU
.
线程
es una unidad de ejecución de programa basada en进程
. En términos sencillos,线程
es un flujo de ejecución en un programa. Un进程
puede tener uno o más线程
.
Solo hay un flujo de ejecución en un进程
llamado单线程
. Es decir, cuando se ejecuta el programa, las rutas del programa tomadas se organizan en orden consecutivo. Las anteriores deben procesarse antes de ejecutar las posteriores.
Múltiples flujos de ejecución en un进程
se denominan多线程
, es decir, se pueden ejecutar múltiples线程
diferentes simultáneamente en un programa para realizar diferentes tareas, lo que significa que un solo programa puede crear múltiples线程
de ejecución paralelos para completar sus respectivas tareas. .
El autor dará un ejemplo simple a continuación. Por ejemplo, si abrimos qq音乐
y escuchamos canciones, qq音乐
puede entenderse como un proceso qq音乐
podemos descargar mientras escuchamos canciones. Las canciones son un hilo y la descarga es un proceso. Si volvemos a abrir vscode
para escribir código, será otro proceso.
Los procesos son independientes entre sí, pero algunos recursos se comparten entre subprocesos del mismo proceso.
El ciclo de vida de un hilo pasa por cinco etapas.
Nuevo estado: después de usar la new
palabra clave y Thread
o su subclase para crear un objeto thread, el objeto thread está en el nuevo estado. Permanece en este estado hasta que el programa start()
el hilo.
Estado listo: cuando el objeto del hilo llama start()
, el hilo entra en el estado listo. El subproceso en estado listo está en la cola listo y puede ejecutarse inmediatamente siempre que obtenga el derecho a usar CPU
.
Estado de ejecución: si el subproceso en el estado listo obtiene recursos CPU
, puede ejecutar run()
y el subproceso está en el estado de ejecución. El hilo en el estado de ejecución es el más complejo, puede bloquearse, estar listo y morir.
Estado de bloqueo: si un hilo ejecuta sleep(睡眠)
, suspend(挂起)
, wait(等待)
y otros métodos, después de perder los recursos ocupados, el hilo entrará en el estado de bloqueo desde el estado de ejecución. Se puede volver a ingresar al estado listo después de que haya expirado el tiempo de suspensión o se hayan obtenido los recursos del dispositivo. Se puede dividir en tres tipos:
bloqueo en espera: el hilo en estado de ejecución ejecuta wait()
, lo que hace que el hilo entre en el estado de bloqueo en espera.
Bloqueo sincrónico: el subproceso no logra adquirir el bloqueo de sincronización synchronized
(porque el bloqueo de sincronización está ocupado por otros subprocesos).
Otro bloqueo: cuando se emite I/O
llamando al hilo sleep()
o join()
, el hilo entrará en el estado de bloqueo. Cuando sleep()
se agota, join()
espera a que el subproceso finalice o se agote el tiempo de espera, o que se complete el procesamiento I/O
, y el subproceso vuelve al estado listo.
Estado de muerte: cuando un subproceso en ejecución completa su tarea u ocurren otras condiciones de terminación, el subproceso cambia al estado terminado.
JS
Como lenguaje de programación del navegador, JS
se utiliza principalmente para interactuar con los usuarios y operar DOM
. Esto determina que solo puede ser de un solo subproceso; de lo contrario, provocará problemas de sincronización muy complejos. Por ejemplo, supongamos que JavaScript
tiene dos subprocesos al mismo tiempo. Un subproceso agrega contenido a un determinado nodo DOM
y el otro subproceso elimina el nodo. En este caso, ¿qué subproceso debería utilizar el navegador?
Cuando JS
analiza un fragmento de código ejecutable (generalmente la fase de llamada de función), primero realizará un trabajo preparatorio antes de la ejecución. Este "trabajo de preparación" se denomina "contexto de ejecución ". (Contexto de ejecución (denominado EC
)" o también se le puede llamar entorno de ejecución .
Hay tres tipos de contexto de ejecución en javascript
, que son:
contexto de ejecución global Este es el contexto de ejecución predeterminado o más básico. Solo habrá un contexto global en un programa y existirá durante todo el ciclo de vida del programa. Script javascript
. La parte inferior de la pila de ejecución no será destruida por la explosión de la pila. El contexto global generará un objeto global (tomando el entorno del navegador como ejemplo, este objeto global es window
) y vinculará this
valor a este objeto global.
Contexto de ejecución de función Siempre que se llama a una función, se crea un nuevo contexto de ejecución de función (independientemente de si la función se llama repetidamente).
Contexto de ejecución de la función eval El código ejecutado dentro de eval
también tendrá su propio contexto de ejecución, pero como eval
no se usa con frecuencia, no lo analizaremos aquí.
Anteriormente mencionamos que js
creará un contexto de ejecución cuando se esté ejecutando, pero el contexto de ejecución debe almacenarse, entonces, ¿qué se usa para almacenarlo? Necesita utilizar la estructura de datos de la pila.
La pila es una estructura de datos de primero en entrar, último en salir.
En resumen, el contexto de ejecución utilizado para almacenar el contexto de ejecución creado cuando se ejecuta el código es la pila de ejecución .
Al ejecutar un fragmento de código JS
Luego, JS
creará un contexto de ejecución global y push
a la pila de ejecución. En este proceso, JS
asignará memoria para todas las variables en este código y asignará un valor inicial (indefinido). El motor JS
entrará en etapa de ejecución, en este proceso JS
ejecutará el código línea por línea, es decir, asignará valores (valores reales) a las variables a las que se les ha asignado memoria una por una.
Si hay function
en este código, JS
creará un contexto de ejecución de función y push
a la pila de ejecución. El proceso de creación y ejecución es el mismo que el contexto de ejecución global.
Cuando se completa una pila de ejecución, el contexto de ejecución se extraerá de la pila y luego se ingresará al siguiente contexto de ejecución.
Permítanme darles un ejemplo a continuación si tenemos el siguiente código en nuestro programa:
console.log("Inicio del contexto de ejecución global"); función primero() { console.log("primera función"); segundo(); console.log("Nuevamente primera función"); } función segunda() { console.log("segunda función"); } primero(); console.log("Fin del contexto de ejecución global");
Analicemos brevemente el ejemplo anterior.
Primero, se creará una pila de ejecución
, luego se creará un contexto global y el contexto de ejecución push
a la pila de ejecución
para iniciar la ejecución. , y Global Execution Context start
encuentra first
método, ejecuta el método, crea un contexto de ejecución de función y push
a la pila de ejecución
para ejecutar first
contexto de ejecución, genera first function
, encuentra second
método, ejecuta el método. , crea un contexto de ejecución de función y push
a la pila de ejecución para
ejecutar second
contexto de ejecución second function
se genera second
contexto de ejecución, se extrae de la pila y se ingresa al first
contexto de ejecución,
first
contexto de ejecución continúa
ejecución, salida Again first function
, se ejecutó la primera función, se sacó de la pila y se ingresó al first
Contexto de ejecución Contexto de ejecución global Contexto
de ejecución global Continuar la ejecución y generar Global Execution Context end
Usamos una imagen para resumir.
está bien. Después de hablar sobre el contexto de ejecución y la pila de ejecución, hablemos sobre el mecanismo de ejecución de js. Hablando del mecanismo de ejecución de js
js
comprender las tareas sincrónicas, asincrónicas, macrotareas y microtareas en js
.
En js
, las tareas se dividen en tareas sincrónicas y tareas asincrónicas. Entonces, ¿qué son las tareas sincrónicas y las tareas asincrónicas?
Las tareas sincrónicas se refieren a tareas en cola para su ejecución en el hilo principal. La siguiente tarea solo se puede ejecutar después de que se haya ejecutado la tarea anterior.
Las tareas asincrónicas se refieren a tareas que no ingresan al hilo principal pero ingresan a la "cola de tareas" (las tareas en la cola de tareas se ejecutan en paralelo con el hilo principal solo cuando el hilo principal está inactivo y la "cola de tareas" notifica al). Hilo principal, una tarea asincrónica Una vez que se puede ejecutar, la tarea ingresará al hilo principal para su ejecución. Debido a que es almacenamiento en cola, cumple con la regla de primero en entrar, primero en salir . Las tareas asincrónicas comunes incluyen nuestro setInterval
, setTimeout
, promise.then
, etc.
introdujo anteriormente tareas sincrónicas y asincrónicas. Ahora hablemos del bucle de eventos.
Las tareas sincrónicas y asincrónicas ingresan a diferentes "lugares" de ejecución respectivamente y ingresan al hilo principal sincrónicamente. Solo cuando se completa la tarea anterior se puede ejecutar la siguiente. Las tareas asincrónicas no ingresan al hilo principal sino que ingresan Event Table
y registran funciones.
Cuando se complete lo especificado, Event Table
moverá esta función a Event Queue
. Event Queue
es una estructura de datos de cola, por lo que cumple con la regla de primero en entrar, primero en salir.
Cuando las tareas en el hilo principal están vacías después de la ejecución, la función correspondiente se leerá de Event Queue
y se ejecutará en el hilo principal.
El proceso anterior se repetirá continuamente, lo que a menudo se denomina Event Loop .
Resumámoslo con una imagen.
Permítanme presentarles brevemente una
función de ejemplo test1() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); console.log("log2"); } test1(); // log1, log2, setTimeout 100, setTimeout 1000
Sabemos que en js, las tareas sincrónicas se ejecutarán primero antes que las asincrónicas, por lo que el ejemplo anterior generará log1、log2
primero,
y luego ejecutará las tareas asincrónicas después de las sincrónicas.
Por
lo tanto, la función de devolución de llamada con un retraso de 100
milisegundos ejecutará la salida setTimeout 100
primero. La
función de devolución de llamada con un retraso de 1000
milisegundos ejecutará la salida setTimeout 1000
más tarde.
Siempre que comprenda las tareas sincrónicas y asincrónicas mencionadas anteriormente por el autor, no habrá ningún problema. Entonces déjenme darles otro ejemplo. Amigos, veamos cuál será el resultado.
función prueba2() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); nueva Promesa((resolver, rechazar) => { console.log("nueva promesa"); resolver(); }).entonces(() => { console.log("promesa.luego"); }); console.log("log2"); } test2();
Para resolver el problema anterior, no basta con conocer las tareas sincrónicas y asincrónicas. También necesitamos conocer las macrotareas y las microtareas.
En js
, las tareas se dividen en dos tipos, una se llama macrotarea MacroTask
y la otra se llama microtarea MicroTask
.
La macrotarea común MacroTask
tiene
el bloque de código principal
setTimeout()
setInterval()
setImmediate() - Node
requestAnimationFrame() - el navegador
La microtarea común MicroTask
tiene
Promise.then()
process.nextTick() - Node
. Ejemplo anterior Implica macrotareas y microtareas. ¿Cuál es el orden de ejecución de las macrotareas y microtareas?
En primer lugar, cuando el script
general (como la primera tarea macro) comienza a ejecutarse, todo el código se dividirá en dos partes: tareas sincrónicas y tareas asincrónicas. Las tareas sincrónicas ingresarán directamente al hilo principal para su ejecución en secuencia. y las tareas asincrónicas ingresarán a la cola asincrónica y luego se dividirán en macrotareas y microtareas.
La macrotarea ingresa Event Table
y registra una función de devolución de llamada en ella. Siempre que se complete el evento especificado, Event Table
moverá esta Event Queue
a la cola de eventos. La microtarea también ingresará a otra Event Table
y se registrará en ella. Siempre que se complete el evento especificado, Event Table
moverá esta función a Event Queue
Cuando se completen las tareas en el hilo principal y el hilo principal esté vacío, se verificará Event Queue
de la microtarea. , todo Ejecutar, si no, ejecutar la siguiente tarea macro.
Usamos una imagen para resumirla.
Después de comprender los ejemplos anteriores de macrotareas y microtareas asincrónicas, podemos obtener fácilmente la respuesta.
Sabemos que en js, las tareas sincrónicas se ejecutarán primero antes que las asincrónicas, por lo que el ejemplo anterior generará log1、new promise、log2
primero. Cabe señalar aquí que el bloque de código principal de la nueva promesa se sincroniza
después de que se ejecuta la macrotarea, y se ejecutarán todas las microtareas generadas por esta macrotarea, por lo que se generará promise.then
. , se ejecutará otra macro tarea, retrasando La función de devolución de llamada de 100
milisegundos priorizará la ejecución y generará setTimeout 100
Esta macrotarea no genera microtareas, por lo que no hay microtareas que deban ejecutarse
para continuar ejecutando la siguiente macrotarea. La función con un retraso de 1000
priorizará la ejecución y generará setTimeout 1000
por lo que después de ejecutar el método test2, generará log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000
en secuencia
. Diferentes opiniones sobre si ejecutar
js
primero con macro tareas y luego micro tareas o con micro tareas antes de macro tareas. El entendimiento del autor es que si todo el bloque de códigojs
se considera una macrotarea, nuestro orden de ejecuciónjs
es primero la macrotarea y luego la microtarea.
Como dice el refrán, practicar una vez es mejor que mirar cien veces. A continuación te daré dos ejemplos. Si puedes hacerlo correctamente, entonces dominas el conocimiento del mecanismo de ejecución js
.
Ejemplo 1
función test3() { consola.log(1); setTimeout(función() { consola.log(2); nueva Promesa(función (resolver) { consola.log(3); resolver(); }).entonces(función () { consola.log(4); }); consola.log(5); }, 1000); nueva Promesa(función (resolver) { consola.log(6); resolver(); }).entonces(función () { consola.log(7); setTimeout(función() { consola.log(8); }); }); setTimeout(función() { consola.log(9); nueva Promesa(función (resolver) { consola.log(10); resolver(); }).entonces(función () { consola.log(11); }); }, 100); consola.log(12); } test3();
Analicémoslo en detalle.
Primero, el bloque de código js
general se ejecuta como una tarea macro y se generan 1, 1、6、12
en secuencia.
Después de ejecutar la macrotarea del bloque de código general, se generan una microtarea y dos macrotareas, por lo que la cola de macrotareas tiene dos macrotareas y la cola de microtareas tiene una microtarea.
Una vez ejecutada la macro tarea, se ejecutarán todas las micro tareas generadas por esta macro tarea. Debido a que solo hay una microtarea, se generarán 7
. Esta microtarea generó otra macrotarea, por lo que actualmente hay tres macrotareas en la cola de macrotareas.
Entre las tres macrotareas, la que no tiene retraso establecido se ejecuta primero, por lo que se genera 8
Esta macrotarea no genera microtareas, por lo que no hay microtareas para ejecutar y la siguiente macrotarea continúa ejecutándose.
Retrase la ejecución de la macrotarea durante 100
milisegundos, genere 9、10
y genere una microtarea, por lo que la cola de microtarea actualmente tiene una microtarea.
Después de ejecutar la macrotarea, se ejecutarán todas las microtareas generadas por la macrotarea, por lo que la microtarea se ejecutará. Todas las microtareas en la cola de tareas generan 11
La ejecución de la macrotarea genera 2、3、5
con un retraso de 1000
milisegundos, y se genera una microtarea. Por lo tanto, la cola de microtarea actualmente tiene una microtarea
. se ejecutará la macrotarea. Se generarán todas las microtareas, por lo que se ejecutarán todas las microtareas en la cola de microtareas y se generará 4
Por lo tanto, el ejemplo de código anterior generará 1、6、12、7、8、9、10、11、2、3、5、4
, 12, 7, 8, 9, 10, 11. , 2, 3, 5, 4 en secuencia. ¿Lo hicieron bien?
Ejemplo 2:
modificamos ligeramente el ejemplo 1 anterior async
await
la función async y await async test4() { consola.log(1); setTimeout(función() { consola.log(2); nueva Promesa(función (resolver) { consola.log(3); resolver(); }).entonces(función () { consola.log(4); }); consola.log(5); }, 1000); nueva Promesa(función (resolver) { consola.log(6); resolver(); }).entonces(función () { consola.log(7); setTimeout(función() { consola.log(8); }); }); resultado constante = esperar async1(); consola.log(resultado); setTimeout(función() { consola.log(9); nueva Promesa(función (resolver) { consola.log(10); resolver(); }).entonces(función () { consola.log(11); }); }, 100); consola.log(12); } función asíncrona async1() { consola.log(13) return Promise.resolve("Promise.resolve"); } test4();
¿Qué generará el ejemplo anterior? Aquí podemos resolver fácilmente el problema de async
y await
.
Sabemos async
y await
son en realidad azúcar de sintaxis para Promise
. Aquí solo necesitamos saber await
es equivalente a Promise.then
. Entonces podemos entender el ejemplo anterior como el siguiente código
function test4() { consola.log(1); setTimeout(función() { consola.log(2); nueva Promesa(función (resolver) { consola.log(3); resolver(); }).entonces(función () { consola.log(4); }); consola.log(5); }, 1000); nueva Promesa(función (resolver) { consola.log(6); resolver(); }).entonces(función () { consola.log(7); setTimeout(función() { consola.log(8); }); }); nueva Promesa(función (resolver) { consola.log(13); return resolver("Promise.resolve"); }).entonces((resultado) => { consola.log(resultado); setTimeout(función() { consola.log(9); nueva Promesa(función (resolver) { consola.log(10); resolver(); }).entonces(función () { consola.log(11); }); }, 100); consola.log(12); }); } test4();
¿Puedes obtener fácilmente el resultado después de ver el código anterior?
Primero, todo el bloque de código js
se ejecuta inicialmente como una tarea macro y genera 1、6、13
en secuencia.
Después de ejecutar la macro tarea del bloque de código general, se generan dos micro tareas y una macro tarea, por lo que la cola de macro tareas tiene una macro tarea y la cola de micro tareas tiene dos micro tareas.
Una vez ejecutada la macro tarea, se ejecutarán todas las micro tareas generadas por esta macro tarea. Entonces se generará 7、Promise.resolve、12
. Esta microtarea generó dos macrotareas más, por lo que la cola de macrotareas actualmente tiene tres macrotareas.
Entre las tres macrotareas, la que no tiene retraso establecido se ejecuta primero, por lo que se genera 8
Esta macrotarea no genera microtareas, por lo que no hay microtareas para ejecutar y la siguiente macrotarea continúa ejecutándose.
Retrase la ejecución de la macrotarea durante 100
milisegundos, genere 9、10
y genere una microtarea, por lo que la cola de microtarea actualmente tiene una microtarea.
Después de ejecutar la macrotarea, se ejecutarán todas las microtareas generadas por la macrotarea, por lo que la microtarea se ejecutará. Todas las microtareas en la cola de tareas generan 11
La ejecución de la macrotarea genera 2、3、5
con un retraso de 1000
milisegundos, y se genera una microtarea. Por lo tanto, la cola de microtarea actualmente tiene una microtarea
. la macrotarea se ejecutará. Todas las microtareas generadas ejecutarán todas las microtareas en la cola de microtareas y generarán 4
Por lo tanto, el ejemplo de código anterior generará 1, 6, 13, 7 1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4
4, ¿lo hicieron bien?
Es posible que muchos amigos aún no comprendan setTimeout(fn)
¿No es obvio que el tiempo de retraso no debería ejecutarse de inmediato?
Podemos entender setTimeout(fn)
como setTimeout(fn,0)
, que en realidad significa lo mismo.
Sabemos que js se divide en tareas sincrónicas y tareas asincrónicas. setTimeout(fn)
es una tarea asincrónica, por lo que incluso si no establece un tiempo de retraso aquí, ingresará a la cola asincrónica y no se ejecutará hasta que se ejecute el hilo principal. inactivo.
El autor lo mencionará nuevamente, ¿crees que el tiempo de retraso que configuramos después de setTimeout
, js
definitivamente se ejecutará de acuerdo con nuestro tiempo de retraso? El tiempo que establecemos es solo para que se pueda ejecutar la función de devolución de llamada, pero si el hilo principal está libre es otra cuestión. Podemos dar un ejemplo simple.
función prueba5() { setTimeout(función() { console.log("setTimeout"); }, 100); sea yo = 0; mientras (verdadero) { yo ++; } } test5(); ¿
El ejemplo anterior definitivamente generará setTimeout
después de 100
milisegundos? No, porque nuestro hilo principal ha entrado en un bucle infinito y no tiene tiempo para ejecutar tareas de cola asincrónicas.
GUI渲染
se menciona aquí. Es posible que algunos amigos no la entiendan. Lo presentaré en detalle en un artículo sobre navegadores más adelante.
Dado que JS引擎线程
y GUI渲染线程
son mutuamente excluyentes, para permitir que宏任务
y DOM任务
avancen de manera ordenada, el navegador iniciará GUI渲染线程
después del resultado de la ejecución de una宏任务
y antes de la ejecución de la siguiente宏任务
, renderice la página.
Por lo tanto, la relación entre macrotareas, microtareas y representación GUI es la siguiente:
tarea macro -> microtarea -> representación GUI -> tarea macro ->...
[Recomendación de videotutorial relacionado: interfaz web]
Lo anterior es un análisis en profundidad de JavaScript Para obtener detalles sobre el contexto de ejecución y el mecanismo de ejecución, preste atención a otros artículos relacionados en el sitio web chino de PHP para obtener más información.