Analice el principio del cierre de javascript, combinado con el motor js de la versión de java para explicar

1. Introducción

Los cierres de JavaScript son un concepto que los programadores de back-end de Java tienen un dolor de cabeza y es difícil de entender, por lo que este artículo analiza el principio de funcionamiento de los cierres basados ​​en el motor js, para que todos puedan tener un conocimiento profundo de los cierres.

2. Por qué se basa en el análisis de la versión java del motor Rhino

  1. java es el mejor idioma
  2. El motor Rhino es la versión java del famoso motor javascript spiderMonkey
Rhino是jdk 1.6自带的js引擎,出自mozilla,其实现原理与firefox的js引擎高度相似
    项目介绍: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino 
    源码地址: https://github.com/mozilla/rhino
最新版本的Nashorn为了执行性能,将js转换成为jvm字节码,
不利于我们剖析javascript的真正运行原理

3. Ejemplos

3.1 Ejemplo de cierre típico

var fun = (function(){
    
    
    var count = 101;
    function addCount(){
    
    
        count+=1;
        return count;
    }   
    return addCount; 
  })();
var result = fun();

Algunos programadores de back-end de Java estarán un poco incómodos con la estructura de código similar a la anterior, porque la función dentro tiene otra capa de funciones, y finalmente devuelve una función, y hay una estructura como (...) (); Podemos poner lo anterior El ejemplo se cambia por el siguiente, o todos pueden entenderlo bien.

function testFun(){
    
    
    var count = 101;
    function addCount(){
    
    
        count+=1;
        return count;
    }   
    return addCount; 
  } 
var result = testFun()();

Después de modificar el ejemplo anterior, es relativamente claro. Después de ejecutar testFun (), el retorno es la función addCount, y agregar otro () es para ejecutar la función addCount ().
Si algunos lectores no están acostumbrados a () (), el código anterior eventualmente se puede reescribir como un ejemplo de versión final

3.2 Ejemplo de versión final

function testFun(){
    
    
    var count = 101;
    function addCount(){
    
    
        count+=1;
        return count;
    }   
    return addCount; 
  } 
var funMethod = testFun();
var result = funMethod();

El último ejemplo anterior es mucho más cómodo que el de los programadores de Java.
Pero algunos programadores aún pueden tener dudas. Según el pensamiento habitual, después de que regresa la función testFun (), después de que se devuelve el resultado, su recuento de variables locales debe reciclarse, por qué la función addCount todavía puede usarla. Si la ejecuta después de el código anterior:

var f1 = testFun();
var f2 = testFun();
console.log("闭包调用结果:"+testFun()());
console.log("f1第1次调用结果:"+f1());
console.log("f1第2次调用结果:"+f1());
console.log("f2第1次调用结果:"+f2());
console.log("f2第2次调用结果:"+f2());

El resultado es:

闭包调用结果:102
f5.html:18 f1第1次调用结果:102
f5.html:19 f1第2次调用结果:103
f5.html:20 f2第1次调用结果:102
f5.html:21 f2第2次调用结果:103

¿Puedes adivinar el resultado de manera correcta y segura? Si puedes, no necesitas leer este artículo. Si adivinas incorrectamente, puedes tomarte unos minutos para continuar leyendo este artículo.

4 Explicación detallada del principio de funcionamiento

4.1 Alcance del objeto de alcance

Javascript es diferente de lenguajes como c, c ++ y java. Las pilas de llamadas a métodos de los lenguajes c, c ++ y java existen en el espacio de subprocesos preasignado del proceso. A medida que el método continúa llamando a la pila se vuelve más y más profundo (pero la parte superior de la pila El valor de sp se hace cada vez más pequeño, porque el espacio de la pila de cada hilo en el espacio de proceso se asigna en la ubicación de la dirección alta, y el espacio del montón se asigna en la dirección baja space), y a medida que el método sale, la pila se vuelve cada vez más superficial.
Pero javascript es un lenguaje interpretado, y su estructura de pila es muy diferente de c, c ++, java.
Cada variable de javascript debe almacenarse en el alcance especificado, excepto para el alcance global, cuando se ejecuta cada función, se creará una pila y un alcance. Alcance del objeto, la función se ejecuta varias veces y se crearán varios alcances. El alcance de
este ejemplo de versión final se muestra en la figura siguiente
Inserte la descripción de la imagen aquí

4.2 ¿Qué hizo var funMethod = testFun ()?

En la superficie, esta línea de código primero ejecuta el método testFun () y finalmente devuelve la función addCount.
Como se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí

4.2.1 Ejecución de la función testFun

  1. Cuando se ejecuta la función testFun, el objeto de alcance correspondiente a su función se creará por defecto
本函数中的局部变量及函数中定义的函数都会记录在这个scope中
  1. Devuelve el objeto de función addCount
返回的addCount函数对象的parentScope会指向
testFun函数所创建的scope对象

4.2.2 Asignar el objeto de función addCount a funMethod

Al final, la función addCount se asignará a la variable funMethod
y la pila de llamadas de la función testFun se destruirá.
Sin embargo, el alcance creado durante la ejecución de la función testFun es referenciado por el parentScope de la función addCount, por lo que escapa al destino de ser destruido

4.3 var resultado = funMethod ()

La ejecución de funMethod aquí es en realidad para ejecutar el objeto de función addCount apuntado por el propio funMethod. Y cuando se ejecuta addCount, no se crea ningún nuevo alcance, pero se usa el alcance como el alcance principal, testFun, por lo que la variable de recuento puede ser usado normalmente

5 Análisis del proceso de ejecución de código

5.1 flujo de ejecución de javascript

5.1.1 Lexing / Tokenización

Este proceso descompone la cadena de caracteres en tokens.

5.1.2 Análisis / análisis

Convierta el método de la unidad léxica en un árbol de sintaxis abstracta (Abstract Syntax Tree AST)

5.1.3 Generar código ByteCode

Genere código IR desde el árbol de sintaxis ast y finalmente genere código byteCode

5.1.4 Ejecutar byteCode en modo interpretado

Ejecutar código byteCode de manera interpretada

5.2 Análisis de caso

Demuestre el principio de funcionamiento del cierre con el ejemplo de versión final

5.2.3 byteCode del bloque de código principal

ICode dump, for null, length = 25
MaxStack = 3
 [0] REG_IND_C0
 [1] CLOSURE_EXPR org.mozilla.javascript.InterpreterData@4617c264
 [2] POP_RESULT
 [3] LINE : 1
 [6] REG_STR_C0
 [7] BINDNAME
 [8] REG_STR_C1
 [9] NAME_AND_THIS
 [10] REG_IND_C0
 [11] CALL 0
 [12] REG_STR_C0
 [13] SETNAME
 [14] POP
 [15] REG_STR_C2
 [16] BINDNAME
 [17] REG_STR_C0
 [18] NAME_AND_THIS
 [19] REG_IND_C0
 [20] CALL 0
 [21] REG_STR_C2
 [22] SETNAME
 [23] POP
 [24] RETURN_RESULT

El número de serie de la PC está entre paréntesis a la izquierda.

5.2.3.1 Crear una pila

Antes de ejecutar este bytecode, debe crear un objeto de pila CallFrame.
Pase CallFrame.initializeArgs e inicialice la pila de CallFrame.

5.2.3.2 Inicializar la pila

  1. En este segmento de código, el alcance usa el alcance global para
    almacenar variables locales como result y funMethod en el alcance actual (global) a través de ScriptRuntime.initScript (...)
  2. De acuerdo con la propiedad itsNestedFunctions del objeto del fragmento de código, verifique si el fragmento de código contiene funciones. Si se llama al método initFunction () para inicializar la función y el objeto de función se almacena en el ámbito actual. En este ejemplo, el El fragmento de código más externo contiene la función testFun
initFunction方法初始化函数时,会设置本函数的父scope,
将上层的prototype赋给本函数的prototype
  1. Asignar espacio de pila

5.2.3.3 Explicación de la ejecución del código de bytes principal

Todas las instrucciones son interpretadas y ejecutadas por Interpreter.interpretLoop ()

[1] CLOSURE_EXPR

Almacene el objeto de la función testFun en la pila [3]

[2] POP_RESULT

frame.result = stack [3] // es para almacenar el objeto de la función testFun en frame.result

[7] BINDNAME

Encuentre el alcance donde se encuentra la variable local funMethod y colóquelo en la pila [3]

[9] NAME_AND_THIS

Busque el objeto testFun en el alcance actual, colóquelo en la pila [4]
y coloque el alcance similar a testFun (es decir, alcance global) en la pila [5]

[11] LLAME 0

La pila instantánea [4] (es decir, el objeto testFun) se almacena en el objeto divertido La
pila instantánea [5] (es decir, el objeto testFun) se almacena en el objeto funThisObj
Inicialice la función testFun para ejecutar el nuevo CallFrame
y finalmente ejecute la función testFun, ingrese el testFun pila para
ver los detalles La descripción byteCode de la función testFun a continuación

[13] SETNAME

Tome el objeto de la función addCount de la pila [4] y guárdelo en la variable local del funMethod del alcance del bloque de código actual

[18] NAME_AND_THIS

pila [4] guardar la función addCount objeto
pila [5] guardar el alcance actual (es decir, el alcance global)

[20] LLAME 0

Llamar y ejecutar la función addCount. El
objeto divertido es la función
addCount . FunThisObj es el alcance global,
ver: Inicializar la pila de funciones addCount.

[22] SETNAME

Establezca 102 en la variable local de resultado del alcance actual

5.2.4 El byteCode de la función testFun

ICode dump, for testFun, length = 14
MaxStack = 2
 [0] LINE : 1
 [3] REG_STR_C0
 [4] BINDNAME
 [5] SHORTNUMBER 101
 [8] REG_STR_C0
 [9] SETNAME
 [10] POP
 [11] REG_STR_C1
 [12] NAME
 [13] RETURN

5.2.4.1 Inicializar la pila de funciones testFun

  1. Cree el alcance de la función testFun (su nombre de clase es NativeCall) y establezca su alcance principal en este alcance, agregue la variable predeterminada: argumentos, el recuento anterior.
  2. Verifique que una nueva función addCount esté definida en esta función, inicialícela y regístrela en este alcance

5.2.4.2 explicación de la ejecución del código de bytes testFun

[4] BINDNAME

Compruebe el alcance que contiene la variable de recuento, devuelva este alcance y guárdelo en la pila [2]

[5] SHORTNUMBER 101
    stack[3] = DBL_MRK;//标识栈这个位置为double
    sDbl[3] = getShort(iCode, frame.pc);//即101,sDbl是栈中存数值的地方
[9] SETNAME

Almacene el valor en la pila [3] en el recuento del alcance de la función testFun

NOMBRE

Tome la función addCount de la función testFun y guárdela en la pila [2]

REGRESO

frame.result = stack [2]
devuelve addCount

5.2.5 byteCode de la función addCount

ICode dump, for addCount, length = 15
MaxStack = 3
 [0] LINE : 1
 [3] REG_STR_C0
 [4] BINDNAME
 [5] REG_STR_C0
 [6] NAME
 [7] ONE
 [8] ADD
 [9] REG_STR_C0
 [10] SETNAME
 [11] POP
 [12] REG_STR_C0
 [13] NAME
 [14] RETURN

5.2.5.1 Inicializar la pila de funciones addCount

  1. El alcance aquí apunta al alcance de la función testFun

5.2.5.2 explicación de la ejecución del bytecode addCount

[6] NOMBRE

Encuentre el valor de la variable de recuento del alcance actual y guárdelo en la pila [1]

[7] UNO
    stack[2] = DBL_MRK;
    sDbl[2] = 1;
[8] AÑADIR

El valor de sDbl [1] se suma a 102

[10] SETNAME

Almacene el valor de sDbl [1] en el recuento del alcance

[13] NOMBRE

Almacene el recuento en el alcance en la pila [0]

[14] VOLVER
    frame.result = stack[0];
    frame.resultDbl = sDbl[0];

Autor: Wu Lian Tian

Supongo que te gusta

Origin blog.csdn.net/vipshop_fin_dev/article/details/109272347
Recomendado
Clasificación