ES6 specification added to the tail call optimization support, although now support situation is not very good, but understand the principle is still very necessary, help us to write efficient code, once a day engine optimization supports the to enhance the performance will harvest. Stack (stack frame) where before the end of the discussion call, look at the time of a function called normally formed Call and the call stack functionLook at a concept: the call stack (Call Stack) , or call call frame (stack frame). I understand the call stack: because the function call, the process will enter into the body of the function is called, this time need to pass some of the parameters that need to be stored into the reference to a location, the other after the end of the function call, execution processes need to return to the place is called, the return to the places where they need to be recorded. So we will call for this function to allocate a storage area for incoming calls when the Senate returns after the end of the address of the call. This storage area is stored data related to calls with this function, so it is the function of the call stack. Consider the following sample code: function g(x) {
return x; // (C)
}
function f(a) {
let b = a + 1;
return g(b); // (B)
}
console.log(f(2)); // (A)
SIMULATED JaavScript engine when executing the process of the above code,
尾调用如果一个函数最后一步是调用另一个函数,则这个调用为尾调用。比如: function g(x) {
return x;
}
function f(y) {
var a = y + 1;
return g(a);
}
但下面这个就不算尾调用了: function g(x) {
return x;
}
function f(y) {
var a = y + 1;
return g(a) + 1;
}
因为这里 function g(x) {
return x;
}
function f(y) {
var a = y + 1;
var tmp = g(a);
var result = tmp + 1;
return result;
}
经过改造后就能很明显看出,其中对 尾调用的判定函数的调用形式首先,JavaScript 中调用函数是多样的,只要这是另一函数中最后一步操作,都可称作尾调用。比如以下的函数调用形式:
表达式中的尾调用因为剪头函数的函数体可以是表达式,所以表达式最终的步骤是什么决定了该剪头函数中是否包含尾调用。 能够形成尾调用的表达式有如下这些,
const a = x => x ? f() : g();
其中
const a = () => f() || g();
上面示例中, const a = () => {
let fResult = f(); // not a tail call
if (fResult) {
return fResult;
} else {
return g(); // tail call
}
};
所以从上面的转换中看出,对于
const a = () => f() && g();
与逻辑或表达式雷同,这里需要对 const a = () => {
let fResult = f(); // not a tail call
if (!fResult) {
return fResult;
} else {
return g(); // tail call
}
};
const a = () => (f() , g());
这个就比较好理解了,逗号表达式是依次执行的,整个表达式返回结果为最后一个表达式。所以这里 需要注意的是,单独的函数调用并不是尾调用,比如下面这样: function foo() {
bar(); // this is not a tail call in JS
}
这里 function foo() {
bar();
return undefined;
}
像这种情况其实并不能简单地通过加一个 尾调用优化回到最开始的那个示例: function g(x) {
return x; // (C)
}
function f(a) {
let b = a + 1;
return g(b); // (B)
}
console.log(f(2)); // (A)
这里 如果仔细看前面调用过程的分析,会发现,在 所以,完全可以省掉 上面优化后的执行场景下,其调用堆栈的分配则变成了:
最最开始不同之处在于,在创建 其好处显而易见,在函数连续调用过程中,堆栈数没有增加。假如不止一次尾调用, 利用这个特性,我们可以将一些不是尾调用的函数想办法改成尾调用,达到优化调用堆栈的目的,这便是尾调用优化。 尾递归调用如果函数的尾调用是对自己的调用,便形成了递归调用,同时还是归调用,所以合称 尾递归调用。相比于传统的递归,调用堆栈极速增加的不同,尾递归调用的调用堆栈是恒定的,这由前面的分析可知。 所以尾调用优化特别适合用于递归的情况,收益会很大。 计算阶乘就是典型的递归场景: function factorial(x) {
if (x <= 0) {
return 1;
} else {
return x * factorial(x-1); // (A)
}
}
但上面这样的实现并不是尾调用。不过可以通过添加一个额外的方法来达到优化成尾调用的目的。 function factorial(n) {
return facRec(n, 1);
}
function facRec(x, acc) {
if (x <= 1) {
return acc;
} else {
return facRec(x-1, x*acc); // (A)
}
}
此时 其他示例利用尾递归调用实现循环语句。 function forEach(arr, callback, start = 0) {
if (0 <= start && start < arr.length) {
callback(arr[start], start, arr);
return forEach(arr, callback, start+1); // tail call
}
}
forEach(['a', 'b'], (elem, i) => console.log(`${i}. ${elem}`));
// Output:
// 0. a
// 1. b
function findIndex(arr, predicate, start = 0) {
if (0 <= start && start < arr.length) {
if (predicate(arr[start])) {
return start;
}
return findIndex(arr, predicate, start+1); // tail call
}
}
findIndex(['a', 'b'], x => x === 'b'); // 1
相关资源 |
Tail call optimization and recursion
Guess you like
Origin www.cnblogs.com/Wayou/p/tail_call_optimization.html
Recommended
Ranking