Analyze the principle of javascript closure--combined with java version js engine to explain

1 Introduction

Javascript closure is a concept that back-end java programmers have a headache and difficult to understand, so this article analyzes the operating principle of closures in principle with the js engine, so that everyone can have a deep understanding of closures.

2. Why is based on the analysis of the java version of the Rhino engine

  1. java is the best language
  2. The Rhino engine is the java version of the famous javascript engine 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. Examples

3.1 Typical closure example

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

Some java back-end programmers will be a little uneasy about the code structure similar to the above. Because the function inside has another layer of functions, and finally returns a function, and there is a structure like (...)();. We can put the above The example is changed to the following, or everyone can understand it well.

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

After modifying the above example, it is relatively clear. After executing testFun(), the return is the addCount function, and adding another () is to execute the addCount() function.
If some readers are not used to ()(), the above code will eventually Can be rewritten as a final version example

3.2 Final version example

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

The final example above is much more comfortable than java programmers.
But some programmers may still have doubts. According to the habitual thinking, after the testFun() function returns, after the result is returned, its local variable count should be recycled, why the addCount function can still use it. If you execute it after the above code :

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());

The result is:

闭包调用结果: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

Can you guess the result correctly and surely? If you can, you don't need to read this article. If you guess incorrectly, you can take a few minutes to continue reading this article.

4 Detailed explanation of operation principle

4.1 Scope object scope

Javascript is different from languages ​​such as c, c++, and java. The method call stacks of c, c++, and java languages ​​exist in the pre-allocated thread space of the process. As the method continues to call the stack becomes deeper and deeper (but the top of the stack The value of sp is getting smaller and smaller, because the stack space of each thread in the process space is allocated in the high address location, and the heap space is allocated in the low address space), and the stack becomes shallower and shallower as the method exits.
But javascript is an interpreted language, and its stack structure is very different from c, c++, java.
Each variable of javascript must be stored in the specified scope, except for the global scope, when each function is executed, a stack and scope will be created Object scope, the function is executed multiple times, and multiple scopes will be created. The scope of
this final version example is shown in the figure below
Insert picture description here

4.2 What did var funMethod = testFun() do?

On the surface, this line of code first executes the testFun() method, and finally returns the addCount function.
As shown in the following figure:
Insert picture description here

4.2.1 Execution of testFun function

  1. When the testFun function is executed, the scope object corresponding to its function will be created by default
本函数中的局部变量及函数中定义的函数都会记录在这个scope中
  1. Return the addCount function object
返回的addCount函数对象的parentScope会指向
testFun函数所创建的scope对象

4.2.2 Assign the addCount function object to funMethod

In the end, the addCount function will be assigned to the funMethod variable
and the call stack of the testFun function will be destroyed.
However, the scope created during the execution of the testFun function is referenced by the parentScope of the addCount function, so it escapes the fate of being destroyed

4.3 var result = funMethod()

The execution of funMethod here is actually to execute the addCount function object pointed to by funMethod itself. And when addCount is executed, no new scope is created, but the scope like the parent scope, testFun, is used, so the count variable can be used normally

5 Code execution process analysis

5.1 javascript execution flow

5.1.1 Lexing/Tokenizing

This process decomposes the character string into tokens.

5.1.2 Parsing/Parsing

Convert the lexical unit method into an abstract syntax tree (Abstract Syntax Tree AST)

5.1.3 Generate ByteCode code

Generate IR code from ast syntax tree, and finally generate byteCode code

5.1.4 Execute byteCode in interpreted mode

Execute byteCode code in an interpreted manner

5.2 Case analysis

Demonstrate the operation principle of closure with the final version example

5.2.3 byteCode of the main code block

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

The pc serial number is in the brackets on the left.

5.2.3.1 Create a stack

Before executing this bytecode, you need to create a stack object CallFrame.
Pass CallFrame.initializeArgs and initialize the CallFrame stack.

5.2.3.2 Initialize the stack

  1. In this code segment, the scope uses the global scope to
    store local variables such as result and funMethod in the current scope (global) through ScriptRuntime.initScript(...)
  2. According to the itsNestedFunctions property of the object of the code snippet, check whether the code snippet contains functions. If the initFunction() method is called to initialize the function, and the function object is stored in the current scope. In this example, the outermost code snippet contains the testFun function
initFunction方法初始化函数时,会设置本函数的父scope,
将上层的prototype赋给本函数的prototype
  1. Allocate stack space

5.2.3.3 Main bytecode execution explanation

All instructions are interpreted and executed by Interpreter.interpretLoop()

[1] CLOSURE_EXPR

Store the testFun function object on stack[3]

[2] POP_RESULT

frame.result = stack[3]//is to store the testFun function object in frame.result

[7] BINDNAME

Find the scope where the funMethod local variable is located, and put it on the stack[3]

[9] NAME_AND_THIS

Find the testFun object in the current scope, put it in stack[4],
and put the testFun-like scope (ie global scope) in stack[5]

[11] CALL 0

Instant stack[4] (ie testFun object) is stored in the fun object
Instant stack[5] (ie testFun object) is stored in the funThisObj object
Initialize the testFun function to execute the new CallFrame
and finally execute the testFun function, enter the testFun stack to
see for details The byteCode description of the testFun function below

[13] SETNAME

Take the addCount function object from stack[4] and store it in the local variable of the funMethod of the current code block scope

[18] NAME_AND_THIS

stack[4] save the addCount function object
stack[5] save the current scope (ie the global scope)

[20] CALL 0

Call and execute the addCount function
fun object is the addCount function
funThisObj is the global scope,
see: Initialize the addCount function stack

[22] SETNAME

Set 102 to the result local variable of the current scope

5.2.4 The byteCode of the testFun function

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 Initialize testFun function stack

  1. Create the scope of the testFun function (its class name is NativeCall), and set its parent scope in this scope, add the default variable: arguments, the previous count.
  2. Check that a new function addCount is defined in this function, initialize it, and record it in this scope

5.2.4.2 testFun bytecode execution explanation

[4] BINDNAME

Check the scope containing the count variable, and return this scope and store it in stack[2]

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

Store the value in stack[3] in the count of the scope of the testFun function

NAME

Take the addCount function from the testFun function and store it in stack[2]

RETURN

frame.result = stack[2]
returns addCount

5.2.5 byteCode of the addCount function

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 Initialize the addCount function stack

  1. The scope here points to the scope of the testFun function

5.2.5.2 addCount bytecode execution explanation

[6] NAME

Find the value of the count variable from the current scope and store it in stack[1]

[7] ONE
    stack[2] = DBL_MRK;
    sDbl[2] = 1;
[8] ADD

The value of sDbl[1] is added to 102

[10] SETNAME

Store the value of sDbl[1] in the count of the scope

[13] NAME

Store the count in the scope in stack[0]

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

Author: Wu Lian Tian

Guess you like

Origin blog.csdn.net/vipshop_fin_dev/article/details/109272347