尾调用
尾调用是函数式变成的一个重要概念,是指函数的最后一步调用另外一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话, 因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上 下文,从而节省了内存,这就是尾调用优化。
需要注意:
1、 ES6 的尾调用优 化只在严格模式下开启,正常模式是无效的。
2、只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行“尾调用优化”。
funtion f(x){
return g(x)
}
这三种情况都不属于尾调用
function f(x) {
let y = g(x)
return y
}
function f(x) {
retrun g(x)+1
}
function f(x) {
g(x)
}//相当与 return undefined
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。但是递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生”栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生”栈溢出”错误。
function factorial(n) {
if(n===1) return 1
return n* factorial(n-1)
}
factorial(5)
上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n)
如果改写成尾递归,只保留一个调用记录,复杂度 O(1)
function factorial(n,tatal) {
if(n===1) return total
return factorial(n-1,n*total)
}
factorial(5,1)//120
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量 total ,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1?
function taiFattorial(n,tatal) {
if(n===1) return total
return taiFactorial(n-1,n*tatal)
}
function factorial(n) {
return tailFactorial(n,1)
}
factorial(5)
上面代码通过一个正常形式的阶乘函数 factorial ,调用尾递归函数 tailFactorial ,看起来就正常多了。
function currying(fn,n) {
return function(m) {
return fn.call(this,m,n)
}
}
function tailFactorial(n,tatal) {
if(n===1)return total
return tailFactorial(n-1,n*total)
}
const factorial = currying(tailFactorial,1)
factorial(5) //120
柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。上面代码通过柯里化,将尾递归函数 tailFactorial 变为只接受1个参数的 factorial 。
function factorial(n,total=1) {
if(n===1) return total
return factorial(n-1,tatal * n)
}
consoole.log(factorial(5))
可以对其中一个参数使用默认值,当我们没有输入该参数时,则就会使用默认值。