Writing in the front
One of the more interesting changes to ES6 functions is the engine optimization of the tail-call system. A tail call is when a function is called as the last statement of another function, like this
function doSomething() {
return doSomethingElse();
}
Copy the code
In ES5, when doSomethingElse is executed, a stack frame is added, and the corresponding stack frame for doSomething is kept in memory, making it prone to programming problems such as stack overflow and other unexpected errors when looped.
Tail call in ES6
Conditions for tail-call optimization in ES6
- In strict mode.
- The last call does not access the current stack frame variable (that is, the function is not a closure)
- Inside a function, the tail call is the last statement.
- The result of the last call is returned as a function value.
The tail call does not create a new stack frame, but clears and reuses the current stack frame if the following conditions are met;
"use strict";
// can be optimized
function doSomething(){/ / after optimizationreturn doSomethingElse();
}
// cannot be optimized, additional operations must be added after the return value
function doSomething(){
return 1+doSomethingElse();
}
// Cannot be optimized, the call is not in the tail
function doSomething(){
var result = doSomethingElse();
return result;
}
// cannot optimize, func is a closure
function doSomething(){
var num=1, func = () = > num;
return func();
}
Copy the code
Tail call case
The main logic of this code is to achieve the function of factorial
function factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1); }}Copy the code
Cannot be optimized because multiplication is performed before recursive calls; If n is a large number, stack overflow is possible;
'use strict';
function factorial_opt(n, p=1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p
return factorial_opt(n - 1, result)
}
}
Copy the code
After optimization, the code uses a parameter to save the last execution result (each call will calculate the result of multiplication), no additional function call is required, and also conforms to the characteristics of optimization;
Verification Results (I)
Open the Google Browser console run code
It doesn’t seem like we wanted to open the Node test code
V10.24.1 $node - v $node foo js/Users/shuai/workspace/foo js: 2function factorial_opt(n, p=1) {
^
RangeError: Maximum call stack size exceeded
at factorial_opt (/Users/shuai/workspace/foo.js:2:23)
...
Copy the code
The magic thing is that the error message appears and the optimized code is not working in line with Google Browser.
Finally, the following result is obtained through Google query, which is transferred from Github Issue Comment
Tail-call optimization was implemented in V8 but abandoned, mainly because implementing tail-call optimization meant modifying the call stack during code execution, which resulted in incomplete information about the execution flow and caused two problems:
For these reasons, the V8 team recommends changing the specification to STC(Syntatic Tail Calls) instead of PTC(Proper Tail Calls). That is, use a specific syntax for trailing calls, such as return continue func(). Currently not supported by V8, it provided — Harmony-explicit-tailcalls and — Harmony-explicit-tailcalls to enable tail-call optimization, but has since been removed.
Verification Results (II)
After a lot of hard work, Safari finally found a way to support tail-call optimizations
The final result is as follows:
- Unoptimized execution results
- Optimized execution result
RangeError: Maximum Call stack size exceeded The optimized code executes normally and efficiently.
Fibonacci numbers
// The general implementation
function fibonacci(n) {
if(n==0 || n == 1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
// After the tail-call is optimized
'use strict'
function fibonacci_opt(n, current = 0, next = 1) {
if(n <= 1) {
return next
}
return fibonacci_opt(n - 1, next, next+current)
}
fibonacci_opt(5)
Copy the code
conclusion
Although tail-call optimization is part of the ES6 specification, it is not implemented in most JS engines. Optimisation -> Proper Tail Calls (Tail Call Optimisation)
Keep tail calls in mind when coding recursion code; In cases where the recursive function is evaluated large enough, tail calls can greatly optimize program performance.