Document directory
- Basic test
- Multiple variables
- Multiple nested Functions
- Variable with the same name declared in nested Functions
- Test Results
- Conclusion
The birth of this article is derived from a topic about closures in javascript recently planned. Due to the need to parse the impact of closures on garbage collection, we have made relevant tests for different javascript Engines.
In order to get the required knowledge from this article, please be clear before reading this articleClosureAndCommon garbage collection algorithmsHave a certain understanding.
Question proposal
Suppose there is the following code:
function outer() { var largeObject = LargeObject.fromSize('100MB'); return function() { console.log('inner'); };}var inner = outer();
In this piece of code,outer
Functions andinner
AClosure, Resulting ininner
Function accesslargeObject
But apparentlyinner
Not accessedlargeObject
In the closurelargeObject
Can an object be recycled?
If more complex situations are introduced:
function outer() { var largeObject = LargeObject.fromSize('100MB'); var anotherLargeObject = LargeObject.fromSize('100MB'); return function() { largeObject.work(); console.log('inner'); };}var inner = outer();
The first obvious concept is:largeObject
Cannot be recycledBecauseinner
You must use it. HoweveranotherLargeObject
Can it be recycled? It will followlargeObject
Always exist together, orlargeObject
Isolated and independently recycled?
Test Method
With this question, we have tested several existing modern javascript Engines, including:
- JScript. dll of IE8
- Chakra of IE9
- Carakan of Opera 11.60
- Chrome 16.0.912.63 comes with V8 (3.6.6.11)
- SpiderMonkey of Firefox 9.0.1
The basic solution for testing is to use code similar to the following:
function outer() { var largeObject = LargeObject.fromSize('100MB'); return function() { debugger; };}var inner = outer();
Use the Developer Tools (Developer Tools, Firebug, and Dragonfly) of various browsers to stop javascript Execution At the breakpoint, and check the function through the console or local variables.largeObject
If the value exists, GC does not recycle the object.
For Some browsers (especially IE), there are two modes for Script execution (Execution Mode and Debugging mode, IE switches through the "Start Debugging" button in the Script Panel of the developer tool ), the breakpoint is hit only in the debugging mode, but different engine optimization schemes may exist in the debugging mode.Memory comparison. Open the resource browservar inner = outer();
Execute a garbage collection task after one row (IE useswindow.CollectGarbage()
; Use of Operawindow.opera.collect();
) To view memory changes. If the memory is always occupied by MB, the GC does not recycle the object.
For the design of use cases, we can know from the ECMAScript standard that all variable access is performed through a LexicalEnvironment object, so the goal is to test in different LexicalEnvironment structures. From the standard, searchLexicalEnvironmentThere are several situations that can easily change the lexicalenvironment structure:
- Enter a function.
- Enter a Section
eval
Code.
- Use
with
Statement.
- Use
catch
Statement.
Therefore, the following is a multi-use case test for these four cases.
Test process-level results basic test code
function outer() { var largeObject = LargeObject.fromSize('100MB'); return function() { debugger; };}var inner = outer();inner();
Test Results
- Jscript. dll-no recovery, no reduction in memory.
- Chakra-reclaim, the memory will be restored
outer
The status before the function is executed.
- Carakan-No recycle, no drop in memory.
- V8-recycle, access
largeObject
Throw a referenceerror.
- Spidermonkey-recycle, access
largeObject
Getundefined
.
Conclusion
When a functionouter
Returns another function.inner
Chakra, V8, and spidermonkeyouter
Butinner
Where V8 directly unbinds the variable from lexicalenvironment, while spidermonkey only sets the value of the variableundefined
, Does not unbind.
Code for multiple variables
function outer() { var largeObject = LargeObject.fromSize('100MB'); var anotherLargeObject = LargeObject.fromSize('100MB'); return function() { largeObject; debugger; };}var inner = outer();inner();
Test Results
- Jscript. dll-no recovery, no reduction in memory.
- Chakra-Recycle
anotherLargeObject
, Memory will returnouter
Before the call, it is increased by about MB.
- Carakan-No recycle, no drop in memory.
- V8-recycle, access
largeObject
Get the correct value, accessanotherLargeObject
Throw a referenceerror.
- Spidermonkey-recycle, access
largeObject
Get the correct value, accessanotherLargeObject
Getundefined
.
Conclusion
When multiple variables are bound to a lexicalenvironment, chakra, V8, and spidermonkey determine whether different variables are used. This method is used to scan the returned functions.inner
The source codeNotinner
Variables usedUnbind from lexicalenvironment (similarly, spidermonkey is not unbound and only assignedundefined
), And the remaining variables are retained.
eval
Impact code
function outer() { var largeObject = LargeObject.fromSize('100MB'); return function() { eval(''); debugger; };}var inner = outer();inner();
Test Results
- Jscript. dll-no recovery, no reduction in memory.
- Chakra-no recovery, no reduction in memory.
- Carakan-No recycle, no drop in memory.
- V8-do not recycle, access
largeObject
You can get the correct value.
- SpiderMonkey-do not recycle, access
largeObject
You can get the correct value.
Conclusion
Ifinner
Used in Functionseval
To avoid unexpected results.
Indirect call
eval
Use Code
function outer() { var largeObject = LargeObject.fromSize('100MB'); return function() { window.eval(''); debugger; };}var inner = outer();inner();
Test Results
- JScript. dll-no recovery, no reduction in memory.
- Chakra-reclaim, the memory will be restored
outer
The status before the function is executed.
- Carakan-No recycle, no drop in memory.
- V8-recycle, access
largeObject
Throw a ReferenceError.
- SpiderMonkey-recycle, access
largeObject
Getundefined
.
Conclusion
Because ecmascript requires indirect callseval
The code will beGLOBAL SCOPECannot accesslargeObject
Variable. Therefore, for indirect callseval
In this case, each JavaScript Engine will handle it in a standard way, regardless of the indirect call.eval
.
Similarly,new Function('return largeObject;')
In this case, due to the standardnew Function
The[[Scope]]
Is a global lexicalenvironment, and thus cannot be accessedlargeObject
, All engines are called indirectlyeval
Method, select ignoreFunction
The call of the constructor.
Use code for multiple nested Functions
function outer() { var largeObject = LargeObject.fromSize('100MB'); function help() { largeObject; // eval(''); } return function() { debugger; };}var inner = outer();inner();
Test Results
- JScript. dll-no recovery, no reduction in memory.
- Chakra-no recovery, no reduction in memory.
- Carakan-No recycle, no drop in memory.
- V8-do not recycle, access
largeObject
You can get the correct value.
- SpiderMonkey-do not recycle, access
largeObject
You can get the correct value.
Conclusion
Not only is it returnedinner
Function, ifouter
Nestedhelp
Used in the functionlargeObject
Variable (or directly calleval
).largeObject
Variable cannot be recycled. Therefore, javascript Engine scans more than justinner
The function source code also scans the source code of all other nested functions to determine whether a specific variable can be unbound.
Use
with
Expression Code
function outer() { var largeObject = LargeObject.fromSize('100MB'); var scope = { o: LargeObject.fromSize('100MB') }; with (scope) { return function() { debugger; }; }}var inner = outer();inner();
Test Results
- JScript. dll-no recovery, no reduction in memory.
- Chakra-Recycle
largeObject
, But do not recyclescope.o
, Memory recoveryouter
The function is increased by about MB before it is called (unknown ).scope
Whether it is recycled ).
- Carakan-No recycle, no drop in memory.
- V8-do not recycle, access
largeObject
Andscope
Ando
You can get the correct value.
- SpiderMonkey-Recycle
largeObject
Andscope
To access the two variables.undefined
, Do not recycleo
To obtain the correct value.
Conclusion
Whenwith
When the expression is used, V8 will discard the collection of all variables and retain the binding of all variables in LexicalEnvironment. While SpiderMonkey will retainwith
All variables in the new LexicalEnvironment generated by the expression are bound.outer
The LexicalEnvironment generated by the function is processed in a standard manner, and the variable binding is removed as much as possible.
Use
catch
Expression Code
function outer() { var largeObject = LargeObject.fromSize('100MB'); try { throw { o: LargeObject.fromSize('100MB'); } } catch (ex) { return function() { debugger; }; }}var inner = outer();inner();
Test Results
- Jscript. dll-no recovery, no reduction in memory.
- Chakra-Reclaim largeobject and ex, and the memory will be restored to the status before the outer function is called.
- Carakan-No recycle, no drop in memory.
- V8-only reclaim largeobject, access largeobject and throw a referenceerror, but still can access ex.
- Spidermonkey-only recycles largeobject. undefined is obtained when accessing largeobject, but ex is still accessible.
Conclusion
catch
Although the expression will add a LexicalEnvironment, it has almost no effect on the algorithm for unbinding variables in the closure. This is becausecatch
The generated LexicalEnvironment only appends a bound catch Error object, which is controllable (relativewith
Therefore, the impact on variable recovery can also be controlled and optimized. However, for LexicalEnvironment with newly generated and added Error objects, V8 and SpiderMonkey will not be further optimized and recycled, while Chakra will process the LexicalEnvironment. If the Error object can be recycled, then it is unbound.
Code used for variable with the same name declared in nested Functions
Function outer () {var largeobject = largeobject. fromsize ('100mb'); return function (largeobject/* or declare */In the function body) {// var largeobject ;};} var inner = outer (); inner ();
Test Results
- Jscript. dll-no recovery, no reduction in memory.
- Chakra-recycle, the memory will be restored to the status before the outer function is called.
- Carakan-No recycle, no drop in memory.
- V8-recycle, the memory will be restored to the status before the outer function is called.
- Spidermonkey-recycle, the memory will be restored to the status before the outer function is called.
Conclusion
When a nested function has a variable or parameter with the same name as an outer function, the optimization of variable recycling in the outer function is not affected. That is, the javascript engine will excludeFormalparameterlistAnd allVariabledeclarationIn the expressionIdentifierAnd then scan allIdentifierTo analyze the recyclability of variables.
Overall conclusion
The first clear conclusion is that the following content will affect the collection of variables in the closure:
- Whether this variable is used in nested functions.
- Nested functions?Direct call
eval
.
- Used?
with
Expression.
Chakra, V8, and spidermonkey will be affected by the above factors and show different and similar recycling policies, while JScript. DLL and carakan are not optimized in this respect at all, and all variables in the entire lexicalenvironment will be fully retained, resulting in a certain amount of memory consumption.
Because chakra, V8, and spidermonkey engines with optimization policies for the collection of variables in the closure are similar in behavior, we can summarize the following: when a function is returnedfn
Hour:
Iffn
Of[[Scope]]
Is ObjectEnvironment (with
Expressions generate ObjectEnvironment, functions, andcatch
Expression to generate DeclarativeEnvironment), then:
- If the engine is V8, the entire process is exited.
- For SpiderMonkey, the outer LexicalEnvironment of the ObjectEnvironment is processed.
Obtain all Function objects under the current LexicalEnvironment. For each Function object, analyze its FunctionBody:
- If FunctionBody containsDirectly call evalTo exit the entire process.
- Otherwise, all identifiers are obtained.
- For each Identifier, set it
name
Find the rule namedname
Bindingbinding
.
- Pair
binding
AddnotSwap
Attribute, whose value istrue
.
Check the binding of each variable in the current LexicalEnvironment. If the binding hasnotSwap
Property and the value istrue
, Then:
- If it is a V8 engine, delete the binding.
- For SpiderMonkey, set the bound value
undefined
, Will be deletednotSwap
Attribute.
For the Chakra engine, it is unknown whether it is in V8 or SpiderMonkey mode.
From the above tests and conclusions, V8 is indeed an excellent JavaScript engine, and the optimization in this aspect is quite in place. The spidermonkey adopts a more friendly method. Instead of directly deleting the variable binding, it assigns the valueundefined
The spidermonkey team may consider someExtremely SpecialThis variable may still be used, so at least a referenceerror will not be thrown to interrupt code execution. Compared with the jscript. dll of IE8, the chakra of ie9 has made great progress, and the detailed processing is also excellent. Opera's carakan is relatively backward in this aspect and has not optimized the variable recycling in the closure at all. It chose the safest but slightly wasteful method.
In addition, all browsers with optimization policies have chosen a balance between overhead and speed, which is exactly why the "multiple nested functions" test case, althoughinner
UnusedlargeObject
Object, even ininner
Inhelp
The function object has been unbound but not unbound.largeObject
. Based on this phenomenon, it can be inferred that each engine only checks the relevance of a layer, that is, it does not processinner -> help -> largeObject
In this way, you can only find the reference relationship in depth.inner -> largeObject
Andhelp -> largeObject
And make a collection to improve efficiency. Maybe there is still a waste of memory overhead in this way, but the CPU resources are also very valuable. How to grasp the balance between them is the choice of the javascript engine.
In addition, based on tests by some developers, Chakra is even eligible to be called an existingThe fastest javascript EngineMicrosoft has been working hard, and developers should not blindly abuse and laugh at IE. We can laugh at IE6's backwardness, and we can't see the contributions that earlier versions of Internet Explorer have made to the development of the Internet. We can relentlessly crack down on these historical products today, however, we should not treat the entire IE series equally and stick the "junk" name. An objective view and evaluation are the most basic principles and qualities that a technician should possess.
----------------------------------------- Original address -----------------------------------------------
Closure and variable recycling