Alcance y cierres de JavaScript

Hablando de cómo los navegadores compilan el código JS

He estado pensando durante mucho tiempo, cuando le damos el código al navegador, cómo el navegador convierte el código en una página web animada. Antes de que el motor JS ejecute nuestro código, lo que el navegador ha hecho a nuestro código, este proceso es como una caja negra para mí, misterioso y curioso.

Entendervar a = 2

Todos los días escribimos var a = 2código JS simple como este, pero el navegador es una máquina, y solo puede reconocer 0s y 1s binarios, lo que var a = 2definitivamente es más difícil para nosotros que los idiomas extranjeros. No importa si tiene dificultades. Al menos nuestro problema está claro ahora. Necesitamos saber cómo puede transformar caracteres humanos significativos en 0 y 1 que cumplan ciertas reglas.

Piense en cómo leemos una oración (piense en un idioma extranjero con el que no estamos tan familiarizados). Cuando no estamos familiarizados con el inglés, en realidad preferimos entender las palabras una por una. Estas palabras se han convertido allí de acuerdo con ciertas reglas. Oraciones significativas El navegador es realmente el caso var a = 2, el navegador realmente ver es que var, a, =, 2esto es una palabra. Este proceso se denomina etapa de análisis léxico, en otras palabras, divide una cadena de caracteres en bloques de código significativos (para el lenguaje de programación).
Al igual que combinamos palabras en oraciones de acuerdo con las reglas gramaticales, el navegador también combinará los bloques de código descompuestos anteriores en un árbol (AST) que representa la estructura gramatical del programa. Esta etapa se llama etapa de análisis gramatical. Ya es un idioma extranjero significativo, pero está a un paso de su comprensión directa de la generación de código y la conversión del código en un lenguaje máquina significativo (lenguaje binario).

Resumimos las tres etapas de la experiencia.

- 词法分析:分解代码为有意义的词语;
* 语法分析:把有意义的词语按照语法规则组合成代表程序语法结构的树(AST);
* 代码生成:将 AST 转换为可执行代码

A través de las tres etapas anteriores, el navegador ya puede ejecutar el código ejecutable que obtuvimos. También hay una llamada colectiva para que las tres etapas realicen la etapa de compilación. Llamamos a la ejecución del código ejecutable después de la fase de ejecución.

¿Cuándo se determina el alcance de JS?

En los lenguajes de programación, generalmente hay dos tipos de alcance, alcance léxico y alcance dinámico. El alcance léxico es el alcance determinado por la estructura del código escrito durante la programación. En términos generales, después de que se completa la compilación, se ha determinado el alcance y el código no cambiará durante el proceso de ejecución. Y el ámbito dinámico escucha el nombre y sabe que el ámbito cambiará dinámicamente durante la ejecución del código. En general, se cree que el alcance de nuestro JavaScript es léxico (en general, porque JavaScript proporciona algunos métodos para cambiar dinámicamente el alcance, que se presentará más adelante).

El alcance léxico es el alcance determinado por la estructura del código escrito durante la programación.Al comparar lo que hace el navegador en la etapa de compilación, encontramos que el alcance léxico se determina en la etapa de compilación. Al ver aquí, ¿comprendió de repente por qué a menudo escuchamos la frase "el alcance de una función se determina en la etapa de definición de la función". A continuación, explicaremos qué reglas determina el alcance de la función.

Alcance en JS

¿Cuál es el alcance?

¿Qué es el alcance? "No sabes js" da ese concepto:

Use un conjunto estricto de reglas para distinguir qué identificadores tienen acceso a esas gramáticas.

Bien, tan abstracto, ¿cuál es el identificador? ¿Cómo entender el alcance? Echemos un vistazo a ellos uno por uno.

Identificador:

Sabemos que cuando nuestro programa se ejecuta, nuestros datos ("cadena", "objeto", "función", etc. se cargan en la memoria). Entonces, ¿cómo accedemos al área de memoria correspondiente? El identificador funcionará en este momento, a través del cual podemos encontrar los datos correspondientes, desde esta perspectiva, los nombres de variables, nombres de funciones, etc. son identificadores.

La operación del
identificador conoce el identificador, pensemos qué operaciones usualmente realizamos en el identificador. De hecho, no hay más de dos tipos. Mira el siguiente código:

// 第一种定义了标识符`a`并把数值2赋值给了`a`这种操作有一个专门的术语叫做`LHS`
var a = 2;

// 第二种,var b = a ,其实对应a ,b 两个操作符是不同的操作,对b来说是一个赋值操作,这是LHS,但是对a来说却是取到a对应的值,这种操作也有一个专门的术语叫做“RHS”
var b = a;

Para resumir, existen las siguientes dos operaciones para identificadores

- 赋值操作(LHS);常见的是函数定义,函数传参,变量赋值等等
* 取值操作(RHS);常见包括函数调用,
Mira hacia atrás en el alcance

Conociendo el identificador y las dos operaciones en el identificador, podemos entender fácilmente el alcance, que en realidad define el alcance de nuestra operación de identificador en tiempo de ejecución, correspondiente al problema real. Es donde podemos llamar funciones o variables con las que estamos familiarizados.

El alcance también puede verse como un conjunto de reglas para encontrar variables basadas en nombres. Entonces echemos un vistazo más de cerca a esta regla: cuando no se puede encontrar una variable en el alcance actual, el motor continuará buscando en el alcance anidado externo hasta que encuentre la variable o alcance el alcance más externo (también Es el alcance global).

El término anidamiento se menciona aquí. Veamos los factores en js que pueden formar un ámbito.

Tipos de alcance en JS

Alcance de la función

El alcance de la función es el alcance más común en js. El alcance de la función nos brinda la experiencia más intuitiva de que las funciones internas pueden llamar variables en funciones externas. Las capas de funciones forman intuitivamente ámbitos anidados. Pero lamento mucho decir esto solo. Todavía recuerdo lo que a menudo escuchamos "si asignamos una variable indefinida dentro de una función, esta variable se transformará en una variable global". Para mí, esta oración fue casi memorizada, y nunca he podido entenderla. Entendemos esta oración desde la perspectiva del funcionamiento del identificador.

var a = 1;

function foo(){ // b第一次出现在函数foo中 b = a ; } foo(); // 全局可以访问到b console.log(b); //1

Cuando llamamos foo(), en realidad realizamos la operación LHS en b (tome el valor de a y asígnelo a b), no hay var let delante de b, por lo que el navegador primero foo()busca el identificador b en el alcance, y el resultado está en b No lo encontré. Instale la regla de alcance. El navegador continuará foo()buscando el identificador b en el alcance externo. El resultado aún no se encuentra, lo que indica que no hay una b definida en el alcance del identificador de consulta b. En el modo no estricto, la operación LHS definirá a b en el nivel más externo (es decir, el global) del rango de búsqueda, por lo que b se convertirá en una variable global (el modo estricto LHS no puede encontrar ReferenceError). Para que esa oración se pueda entender. También vale la pena señalar que las operaciones de RHS en los operadores tendrán diferentes situaciones. Ya sea que no se pueda encontrar RHS en modo estricto o no estricto, devuelva un error de Error de referencia (las operaciones no razonables en el valor encontrado por RHS devolverán un error TypeError(discriminación de alcance Operación exitosa ilegal.)).

Cierres: los cierres son el resultado natural de escribir código basado en el alcance léxico. Ni siquiera necesita crear conscientemente cierres para usarlos. La creación y el uso de cierres están en todas partes en su código. Lo que te falta es un entorno de pensamiento que reconozca, abrace e influya en los cierres de acuerdo con tus propios deseos.

Alcance del bloque

Además del alcance de la función, JS también proporciona alcance de bloque. Deberíamos tener claro que el alcance es para identificadores, y el alcance de bloque limita los identificadores {}a.

ES6 proporcionado let, consta los identificadores de método se fijan al bloque de declaración. A menudo ignoramos try/catchla catchdeclaración creará un ámbito de bloque.

Método para cambiar el alcance de la función

En términos generales, el alcance léxico se ha determinado durante la etapa de compilación del código. Esta certeza es realmente muy beneficiosa. Durante la ejecución del código, puede predecir cómo encontrarlos durante la ejecución. Puede mejorar la eficiencia de ejecución del código durante la fase de ejecución. Pero JS también proporciona una forma de cambiar dinámicamente el alcance. eval()Funciones y withpalabras clave.

eval()Método:
este método acepta una cadena como parámetro y trata el contenido como si fuera el código que existía en esta posición en el programa en el momento de la escritura. En otras palabras, puede usar el programa para generar código y ejecutarlo en el código que escribió, como si el código estuviera escrito en esa ubicación.

 function foo(str,a){
     eval(str);//欺骗作用域,词法阶段阶段foo()函数中并没有定义标识符,但是在函数运行阶段却临时定义了一个b; console.log(a,b); } var b = 2; foo("var b =3;",1);//1,3 // 严格模式下,`eval()`会产生自己的作用域,无法修改所在的作用域 function foo(str){ 'use strict'; eval(str); console.log(a);//ReferenceError: a is not de ned } foo('var a =2');

eval()A veces es muy útil, pero el consumo de rendimiento es muy grande, también puede traer riesgos de seguridad, por lo que no se recomienda.

withPalabras clave:

a menudo se usa como un atajo para referirse repetidamente a múltiples atributos en el mismo objeto.

    var obj = { 
        a: 1,
      b: 2, c: 3 }; // 单调乏味的重复 "obj" obj.a = 2; obj.b = 3; obj.c = 4; // 简单的快捷方式 with (obj) { a = 3; b = 4; c = 5; } function foo(obj) { with (obj) { a = 2; } } var o1 = { a: 3 }; var o2 = { b: 3 }; foo( o1 ); console.log( o1.a ); // 2 foo( o2 ); console.log( o2.a ); // undefined console.log( a ); // 2——不好,a被泄漏到全局作用域上了! // 执行了LHS查询,不存在就在全局创建了一个。 // with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。 

withTambién causará pérdida de rendimiento.

El motor de JavaScript realiza varias optimizaciones de rendimiento durante la fase de compilación. Algunas de estas optimizaciones se basan en la capacidad de realizar análisis estáticos basados ​​en la morfología del código y predeterminar las posiciones de definición de todas las variables y funciones para encontrar rápidamente el identificador durante la ejecución.

Promoción de declaración

El alcance está relacionado con el alcance del identificador, y el alcance del identificador está estrechamente relacionado con su posición de declaración. En jshay algunas palabras clave se dedican a (como el identificador comunicado var, let, const), declarará no anónimo define identificador de función.

Con respecto a la declaración, todos pueden haber oído hablar de la palabra promoción. Analicemos los motivos de la promoción de la declaración.

Ya sabemos que el motor compilará el código JavaScript antes de interpretarlo. Parte del trabajo en la fase de compilación es encontrar todas las declaraciones y asociarlas con el alcance apropiado (el núcleo del alcance léxico).
En este caso, la declaración parece haber sido mencionada anteriormente.
Vale la pena señalar que cada alcance será promovido. La declaración será promovida a la parte superior del alcance.

Sin embargo, no todas las declaraciones serán promovidas, y las diferentes declaraciones tendrán pesos diferentes. Específicamente, las declaraciones de función serán promovidas y las expresiones de función no serán promovidas (incluso las expresiones de función nombradas no serán promovidas).

Por var la definición de las variables de aumentar, mientras que lete consthizo una declaración no mejora.

Se promoverán tanto las declaraciones de funciones como las declaraciones de variables. Pero un detalle digno de mención es que la función se promocionará primero, y luego la variable, es decir, si una declaración de variable y una declaración de función tienen el mismo nombre, incluso si la declaración de variable es la primera en el orden de la instrucción, el identificador seguirá apuntando a la función relevante. Función.

Si una variable o función tiene declaraciones repetidas, será la primera declaración.

El último punto a tener en cuenta es que la
declaración en sí será promovida, y las operaciones de asignación que incluyen la asignación de expresiones de función no serán promovidas.

Algunas aplicaciones de alcance

Al ver esto, creo que todos deberían tener una comprensión más detallada del alcance de JS. Hablemos de algunas aplicaciones extendidas de alcance JS.

Principio de menor privilegio

También se llama principio de autorización mínima o exposición mínima. Este principio significa que en el diseño de software, el contenido necesario debe estar expuesto a un mínimo, y otro contenido debe estar "oculto", como el diseño API de un módulo u objeto. Eso es privatizar parte del código tanto como sea posible.

Las funciones pueden generar su propio alcance, por lo que podemos utilizar el método de encapsulación de funciones (tanto expresiones de funciones como declaraciones de funciones) para lograr este principio.

    // 函数表达式
    var a = 2;
    (function foo() { // <-- 添加这一行 var a = 3; console.log(a); // 3 })(); // <-- 以及这一行 console.log( a ); // 2

Aquí, por cierto, explica cómo distinguir entre expresiones de función y declaraciones de función:

Si función es la primera palabra en la declaración, entonces es una declaración de función, de lo contrario es una expresión de función.
La diferencia más importante entre las declaraciones de función y las expresiones de función es dónde se vincularán sus identificadores de nombre. Las expresiones de función pueden ser anónimas, y las declaraciones de función no pueden omitir nombres de funciones; esto es ilegal en la sintaxis de JavaScript.

Se puede encapsular utilizando la expresión de función ejecutada inmediatamente (IIFE).

Expresión de función ejecutada inmediatamente (IIFE)

    var a = 2;
    (function foo() { var a = 3; console.log(a); // 3 })(); console.log(a); // 2

La expresión de la función se ejecutará inmediatamente después de agregar un paréntesis.

(function(){ .. }())Es otra expresión de IIFE. Los corchetes se agregan dentro y fuera, y la función es la misma.

Por cierto, otro uso avanzado muy común de IIFE es tratarlos como llamadas a funciones y pasar parámetros.

    var a = 2;
    (function IIFE(global) { var a = 3; console.log(a); // 3 console.log( global.a ); // 2 })(window); console.log(a); // 2
Cierre

En general, todos describirían los cierres de esta manera.

Cuando el valor de retorno de una función es otra función, si la función devuelta llama a otras variables dentro de su función principal, si la función devuelta se ejecuta externamente, se genera un cierre.

    function foo() {
        var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // 2 —— 这就是闭包的效果。在函数外访问了函数内的标识符 // bar()函数持有对其父作用域的引用,而使得父作用域没有被销毁,这就是闭包

En términos generales, debido a la existencia del mecanismo de recolección de basura, la función se destruirá después de que se complete la ejecución y el espacio de memoria que ya no se usa. En el ejemplo anterior, dado que parece que  foo()el contenido ya no se usará, es natural considerar reciclarlo. El lugar "mágico" de los cierres es que puede evitar que esto suceda (alguien solía decir que debería reducir el uso de cierres y el miedo a las pérdidas de memoria. De hecho, esto no es demasiado preocupante).

De hecho, según la definición anterior, lo supe por mucho tiempo, pero al mismo tiempo también pensé erróneamente que rara vez uso cierres, porque realmente no los utilicé activamente, pero de hecho, estaba equivocado, involuntariamente. Siempre use cierres.

Esencialmente, cada vez que trate las funciones (accediendo a sus respectivos ámbitos léxicos) como tipos de valor de primer nivel y las pase a todas partes, verá la aplicación de cierres a estas funciones. En temporizadores, oyentes de eventos, solicitudes de Ajax, comunicación entre ventanas, trabajadores web o cualquier otra tarea asíncrona (o síncrona), siempre que se utilice la función de devolución de llamada
, ¡en realidad está utilizando cierres! ¡ Así que debe saber que tiene Cierres usados ​​muchas veces.

Aquí hay un pozo que todos pueden haber encontrado, un pozo causado por la falta de comprensión adecuada del alcance y el cierre.

    for (var i = 1; i <= 5; i++) {
        setTimeout(function timer() { console.log(i); }, i * 1000); } // 其实我们想得到的结果是1,2,3,4,5,结果却是五个6

Analicemos las razones de este resultado:
tratamos de suponer que cada iteración del ciclo "capturará" una copia de i en tiempo de ejecución. Pero de acuerdo con el principio de funcionamiento del alcance, la situación real es que, aunque las cinco funciones en el bucle se definen en cada iteración (la primera definición se menciona principalmente antes, la última se ignorará), pero todas están cerradas. En un ámbito global compartido, porque cuando ha llegado el momento de ejecutar la función del temporizador, la i en el global es 6, por lo que no puede cumplir con las expectativas.

Comprenda el problema del alcance, aquí tenemos dos soluciones:

    // 办法1
    for (var i = 1; i <= 5; i++) { (function(j) { setTimeout(function timer() { console.log(j); }, j * 1000); })(i); //通过一个立即执行函数,为每次循环创建一个单独的作用域。 } // 办法2 for (var i = 1; i <= 5; i++) { let j = i; // 是的,闭包的块作用域! setTimeout( function timer() { console.log(j); }, j * 1000); } // let 每次循环都会创建一个块作用域

El desarrollo actual es inseparable de la modularidad. Hablemos de cómo los módulos usan cierres.

Cómo los módulos usan los cierres: la forma
más común de implementar el patrón del módulo a menudo se denomina exposición del módulo.

Veamos cómo definir un módulo.

    function CoolModule() {
        var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log(something); } function doAnother() { console.log(another.join(" ! ")); } // 返回的是一个对象,对象中可能包含各种函数 return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); // 在外面调用返回对象中的方法就形成了闭包 foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3

Dos condiciones necesarias del módulo:

  • Debe haber una función cerrada externa, que debe llamarse al menos una vez

  • La función cerrada debe devolver al menos una función interna, de modo que la función interna pueda formar un cierre en el ámbito privado y pueda acceder o modificar el estado privado.

El artículo está por terminar aquí, gracias por leer, espero que ganes algo.

Supongo que te gusta

Origin www.cnblogs.com/funtake/p/12733066.html
Recomendado
Clasificación