递归调用与尾调用

// 普通递归函数的调用 时间复杂度为 O(n)

function fn(num){
  if(num === 1) return 1;
    return num * fn (num -1);
}
// 等同于 该函数耦合性更小
function fn(num){
  if(num === 1) return 1;
  return num * arguments.callee(num - 1);
}

console.log(fn(50)) // 120

// 尾调用函数 优化了递归调用 事件复杂度为O(1)
function ofn (num, sum){
  if(num === 1) return sum;
  return ofn(num - 1, num * sum);
}
console.log(ofn(5, 1)); // 120

// 当然为了优化尾调用函数,我们可以多加一个函数调用的步骤
function ofn1(num){
  return ofn(num, 1);
}
console.log(ofn1(5)); // 120

得出的结果都是一样的,这样优化了前面函数的调用参数值不直观的优化。
不太明白的伙伴们会问,为什么递归调用还需要传入第二个参数,所以ofn1函数就是对上一个函数的优化。
这样我们就可以递归调用传入一个参数从而得到递归后的值!

当然了,看了《javascript高级程序设计(第三版)》这一本书中有讲到“柯里化”的概念,

  它的主要的优点就是:1.参数复用,2.加载一次多次运行,3.延迟运行,等

例如:
柯里化函数参数转换:
var currying = function(fn) {
  // 从第二个传入的参数开始截取,隔离第一个参数
  var args1 = [].slice.call(arguments, 1);
  // 返回一个函数(此处的函数为一个内部函数)外部调用的时候,只是会以函数表达式的形式展现出来
  return function() {
    var args2 = args1.concat([].slice.call(arguments));
    return fn.apply(null, args2);
  };
};
/* 注释 */
// alert(currying) // 指的是当前整个函数currying
// alert(currying()) // 指的是内部返回的匿名函数 function(){}

// "被代理函数"
function proxied ([ ... options ]) {// do something...}

// 在此出我把这一层理解为“代理层(对函数默认参数的传定)”(如有理解误区,望大牛指点)
var proxy = currying (proxied, [ ... ]); // 参数传入 默认传参项,

// 最终只需要调用代理函数传入部分,或则最终的参数
proxy([ ... ]);

返回函数的优点:在程序第一次执行的时候,就把返回的函数给确定,当再次加载该函数的时候,
    就不需要再次去判断什么的,直接到内存中抓取并使用。这样做的目的主要是优化性能。(在此处可能并没有得到多大的体现)
    但是在此处返回一个带参数的函数,中间就多出来一层函数的调用,中间层就可以做“代理”的用途了

言归正传:
  我们的例如就是将多个参数化为一个参数的形式就行传参,将其他的参数在另一个域中进行初始化了!
正真的例如来了:
// 按照柯里化的概念设计一个柯里化函数。

// 函数表达式一:①
function currying (func) {
var args1 = [].slice.call(arguments, 1);
  return function() {
    var args2 = args.concat([].slice.call(arguments));
    /*
    *
将传入进来的参数进行倒叙,但是有个问题,
    * 在柯里化的函数里最好不要修改原型 可以从下面调用的函数进行想办法
    */
    // return func.apply(null, args2.reverse());
    return func.apply(null, args2); // 保留原型
  };
};

/**函数表达式二:②
* 本来打算这种形式进行尾处理优化,因为更具柯里化currying函数的规则,
* 我们需要把函数的参数的位置进行更改一下(该函数不能与上面柯里化函数并用,需要调用下面一个更改后的函数)
*/
function ofn (num, sum){
  if(num === 1) return sum;
  return ofn(num - 1, num * sum);
}

/**函数表达式三:③
* 修改之后的函数表达式,
*/
function ofn(sum, num) {
  if (num === 1) return sum;
  return ofn(num * sum, num - 1);
}

// 函数表达式三:④
// 调用currying函数,进行传入默认的参数
var final = currying(ofn, 1);

// 函数表达式三:⑤
// 最终调用的形式
final(5); // 120

组合表达式:①+③+④+⑤

或利用简单一点的柯里化表达式:
// 函数表达式三:⑥
function currying (proxyFn, second){
  return function (another) {
    return proxyFn.call(this, another, second);
  }
}

组合:⑥+②+④+⑤

当然了还有更简单的方法来实现,就是ECMAScript6标准,部分浏览器实现了一些ES6的特性
function recu (num, sum = 1) {
  if (num === 1) {
    return sum;
  }
  return recu (num - 1, num * sum);
}
函数调用: recu (5) // 120

参考资料:

ECMAScript 6

js中的柯里化

时间: 2024-10-28 19:55:47

递归调用与尾调用的相关文章

尾调用及递归优化

ES6 规范中添加了对尾调用优化的支持,虽然目前来看支持情况还不是很好,但了解其原理还是很有必要的,有助于我们编写高效的代码,一旦哪天引擎支持该优化了,将会收获性能上的提升. 讨论尾调用前,先看函数正常调用时其形成的堆栈(stack frame)情况. 函数的调用及调用堆栈 先看一个概念:调用堆栈(call stack),或叫调用帧(stack frame). 我理解的调用堆栈:因为函数调用后,流程会进入到被调用函数体,此时需要传入一些入参,这些入参需要存放到一个位置,另外当函数调用结束后,执行

尾调用

转自: http://www.ruanyifeng.com/blog/2015/04/tail-call.html https://stackoverflow.com/questions/310974/what-is-tail-call-optimization 一.什么是尾调用? 尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数. function f(x) {returng(x);} 上面代码中,函数f的最后一步是调用函数g,这就叫尾调用. 以下两种情况,都不

函数式编程-尾递归、尾调用

一.什么是尾调用? 尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数. function f(x){ return g(x); } 上面代码中,函数f的最后一步是调用函数g,这就叫尾调用. 以下两种情况,都不属于尾调用. // 情况一 function f(x){ let y = g(x); return y; } // 情况二 function f(x){ return g(x) + 1; } 上面代码中,情况一是调用函数g之后,还有别的操作,所以不属于尾调用,

lua报错,看到报错信息有tail call,以为和尾调用有关,于是查了一下相关知识

尾调用是指在函数return时直接将被调函数的返回值作为调用函数的返回值返回,尾调用在很多语言中都可以被编译器优化, 基本都是直接复用旧的执行栈, 不用再创建新的栈帧, 原理上其实也很简单, 因为尾调用在本质上看的话,是整个子过程调用的最后执行语句, 所以之前的栈帧的内容已经不再需要, 完全可以被复用.报错的回溯日记,因为旧的执行栈已经没了,所以报错日记只显示(tail call).一般调用栈的长度为1M到2M,保存了调用过程中的参数和相关环境,如果递归调用太长,就会溢出.尾调用就能解决递归函数

尾调用优化

参考:http://www.ruanyifeng.com/blog/2015/04/tail-call.html 感谢阮老师. 尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数. //正确的尾调用 function f(x) { if (x > 0) { return m(x) } return n(x); } function f(x){ return g(x); } //非尾调用 // 情况一 function f(x){ let y = g(x); retu

栈溢出 缓冲区溢出的一种 缓冲区以外的存储单元被改写 优化方法之一:尾调用

个人:尾调用时函数式编程的一个重要概念, 栈溢出: 函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息.如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧.等到B运行结束,将结果返回到A,B的调用帧才会消失.如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推.所有的调用帧,就形成一个"调用栈"(call stack).递归调用非常耗内存. 尾调用: 就是指某

Lua 函数、闭包、尾调用总结

<lua 程序设计>在线阅读:http://book.luaer.cn/ 1.函数 函数有两种用途: 完成指定的任务,这种情况下函数作为调用语句使用: 计算并返回值,这种情况下函数作为赋值语句的表达式使用. 1.1 语法 function func_name (arguments-list) statements-list; end; 示例 function foo (x) return 2*x end foo = function (x) return 2*x end 从上面我们可以看出lu

尾调用优化和尾递归改写

1 尾调用 尾调用就是指某个函数的最后一步是调用另一个函数. # 是尾调用 def f(x): return g(x) # 不是尾调用,因为调用函数后还要执行加法,加法才是最后一步操作 def f(x): return 1+g(x) 2 尾调用优化 函数调用有一个调用栈,栈内保存了这个函数内部的变量信息.函数掉用就是切换不同的调用帧,从而保证每个函数有独立的运行环境.因为尾调用是函数的最后一步操作,所以在进入被尾调用函数之前并不需要保留外层函数的运行时环境,因为调用位置.内部变量等信息都不会再用

学习Javascript之尾调用

前言 本文2433字,阅读大约需要10分钟. 总括: 本文介绍了尾调用,尾递归的概念,结合实例解释了什么是尾调用优化,并阐述了尾调用优化如今的现状. 参考文章:尾递归的后续探究 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍 事亲以敬,美过三牲. 正文 尾调用是函数式编程的一个重要的概念,本篇文章就来学习下尾调用相关的知识. 尾调用 在之前的文章理解Javascript的高阶函数中,有说过在一个函数中输出一个函数,则这个函数可以被成为高阶函数.本文的主角尾调用和它类似,如果一个