Comencemos con una pregunta sencilla:
<script type="text/javascript">
alerta(yo); // ?
var i = 1;
</script>
El resultado de salida no está definido. Este fenómeno se denomina "análisis previo": el motor de JavaScript analizará primero las variables var y las definiciones de funciones. El código no se ejecuta hasta que se completa el análisis previo. Si una secuencia de documentos contiene varios segmentos de código de script (código js separado por etiquetas de script o archivos js importados), el orden de ejecución es:
paso 1. Lea el primer segmento de código.
Paso 2. Realice un análisis de sintaxis. Si hay un error, se informará un error de sintaxis (como corchetes que no coinciden, etc.) y saltará al paso 5.
paso 3. Realice un "análisis previo" de las definiciones de funciones y variables var (nunca se informarán errores, porque solo se analizan las declaraciones correctas)
Paso 4. Ejecute el segmento de código e informe un error si hay un error (por ejemplo, la variable no está definida)
Paso 5. Si hay otro segmento de código, lea el siguiente segmento de código y repita el paso 2.
Paso 6. Al final del análisis anterior, se han podido explicar muchos problemas, pero siempre siento que falta algo. Por ejemplo, en el paso 3, ¿qué es exactamente el "análisis previo"? Y en el paso 4, observe el siguiente ejemplo:
<script type="text/javascript">
alerta(i); // error: i no está definido.
yo = 1;
</script>
¿Por qué la primera oración causa un error? En JavaScript, ¿no es necesario que las variables no estén definidas?
El tiempo del proceso de compilación pasó como un caballo blanco, y abrí los "Principios de compilación" al lado de la estantería como si estuviera a un mundo de distancia. Había esta nota en el familiar pero desconocido espacio en blanco:
Para lenguajes compilados tradicionales. Los pasos de compilación se dividen en: análisis léxico y análisis de sintaxis, verificación semántica, optimización de código y generación de bytes.
Pero para los lenguajes interpretados, una vez que se obtiene el árbol de sintaxis mediante análisis léxico y análisis de sintaxis, puede comenzar la interpretación y la ejecución.
En pocas palabras, el análisis léxico consiste en convertir un flujo de caracteres (flujo de caracteres) en un flujo de tokens (flujo de tokens), como convertir c = a - b a:
NOMBRE "c"
;
IGUAL
NOMBRE "un"
MENOS
NOMBRE "b"
PUNTO Y COMA
Los anteriores son solo ejemplos. Para obtener más información, consulte Análisis léxico.
El Capítulo 2 de "La guía definitiva de JavaScript" habla sobre la estructura léxica, que también se describe en ECMA-262. La estructura léxica es la base de un idioma y es fácil de dominar. En cuanto a la implementación del análisis léxico, esa es otra área de investigación y no se explorará aquí.
Podemos usar la analogía del lenguaje natural. El análisis léxico es una traducción difícil uno a uno. Por ejemplo, si un párrafo del inglés se traduce al chino palabra por palabra, lo que obtenemos es un montón de flujos de tokens, lo cual es difícil. para entender. Una traducción adicional requiere un análisis gramatical. La siguiente figura es un árbol de sintaxis de una declaración condicional:
Al construir el árbol de sintaxis, si se descubre que no se puede construir, como si (a {i = 2;}, se informará un error de sintaxis y finalizará el análisis de todo el bloque de código. Este es el paso 2 en Al comienzo de este artículo,
a través del análisis de sintaxis, construya Después del árbol de sintaxis, la oración traducida aún puede ser ambigua y se requiere una verificación semántica adicional. Para los lenguajes tradicionales fuertemente tipados, la parte principal de la verificación semántica es la verificación de tipos, como la verificación de tipos. parámetros reales de las funciones y si los tipos de parámetros formales coinciden. Para lenguajes con tipos débiles, es posible que este paso no esté disponible (tengo energía limitada y no tengo tiempo para observar la implementación del motor JS, por lo que no estoy seguro de si existe una). Paso de verificación semántica en el motor JS)
. Resulta que para los motores JavaScript, debe haber análisis léxico y análisis de sintaxis, y luego puede haber pasos como la verificación semántica y la optimización del código después de completar estos pasos de compilación (cualquier idioma tiene). un proceso de compilación, pero los lenguajes interpretados no se compilan en código binario), el código comenzará a ejecutarse.
El proceso de compilación anterior aún no puede explicar el "análisis previo" al principio del artículo. El proceso del código JavaScript,
dijo Zhou Aimin en "La esencia del lenguaje JavaScript". La segunda parte de "Práctica de programación" tiene un análisis muy cuidadoso de esto:
A través de la compilación, el código JavaScript ha sido. se traduce a un árbol de sintaxis y luego se ejecuta inmediatamente de acuerdo con el árbol de sintaxis,
lo que requiere una ejecución adicional. Comprender el mecanismo de alcance de JavaScript JavaScript usa el alcance léxico, el alcance de las variables de JavaScript se determina cuando se definen. en lugar de cuando se ejecutan, es decir, el alcance léxico depende del código fuente. El compilador puede determinarlo mediante análisis estático, por lo que el alcance léxico también se denomina alcance estático. y eval no se puede implementar solo a través de tecnología estática. De hecho, solo podemos hablar del mecanismo de alcance de JS.
Cuando el motor JS ejecuta cada instancia de función, crea un contexto de ejecución. objeto de llamada. El objeto de llamada es una estructura scriptObject que se utiliza para guardar la tabla de variables internas, como varDecls, tabla de funciones integradas funDecls y upvalue de la lista de referencia principal (nota: información como varDecls y funDecls se obtienen durante el proceso). etapa de análisis de sintaxis y se guardan en el árbol de sintaxis. Cuando se ejecuta la instancia de función, esta información se copiará del árbol de sintaxis a scriptObject (scriptObject es un sistema estático relacionado con la función, consistente con el ciclo de vida de la instancia de función). .
El alcance léxico es el mecanismo de alcance de JS y también es necesario comprender su método de implementación. Esta es la cadena de alcance. La cadena de alcance es un mecanismo de búsqueda de nombres. Primero busca el scriptObject en el entorno de ejecución actual. Si no lo encuentra, sigue el valor ascendente hasta el scriptObject principal y busca el objeto global.
Cuando se ejecuta una instancia de función, se crea o se asocia un cierre con ella. scriptObject se utiliza para guardar estáticamente tablas de variables relacionadas con funciones, mientras que el cierre guarda dinámicamente estas tablas de variables y sus valores en ejecución durante la ejecución. El ciclo de vida de un cierre puede ser más largo que el de una instancia de función. La instancia de función se destruirá automáticamente después de que la referencia activa quede vacía, y el motor JS reciclará el cierre después de que la referencia de datos quede vacía (en algunos casos, no se reciclará automáticamente, lo que provocará una pérdida de memoria).
No se deje intimidar por el conjunto de sustantivos anteriores. Una vez que comprenda los conceptos de entorno de ejecución, objeto de llamada, cierre, alcance léxico y cadena de alcance, muchos fenómenos en el lenguaje JS se pueden resolver fácilmente.
Resumen En este punto, las preguntas al comienzo del artículo se pueden explicar muy claramente:
el llamado "análisis previo" en el paso 3 en realidad se completa en la etapa de análisis de sintaxis del paso 2 y se almacena en el árbol de sintaxis. Cuando se ejecuta una instancia de función, varDelcs y funcDecls se copiarán del árbol de sintaxis al scriptObject del entorno de ejecución.
En el paso 4, las variables no definidas significan que no se pueden encontrar en la tabla de variables de scriptObject. El motor JS buscará hacia arriba a lo largo del valor superior de scriptObject. Si no se encuentra ninguno, la operación de escritura i = 1 finalmente será equivalente a ventana. i = 1; agrega un nuevo atributo al objeto de ventana. Para las operaciones de lectura, si no se puede encontrar el scriptObject rastreado hasta el entorno de ejecución global, se producirá un error de tiempo de ejecución.
Después de comprenderlo, la niebla se disipó, las flores florecieron y el cielo se aclaró.
Para finalizar os dejo una pregunta:
<script type="text/javascript">
var arg = 1;
función foo (arg) {
alerta(arg);
var arg = 2;
}
foo(3);
</script>
¿Cuál es el resultado de la alerta?