Es6 复阅(9-2)(部分是非es6的) --函数的扩展(尾调用,尾递归)

1.尾调用优化

属于尾调用的 — 即只保留内层函数的调用帧,节省内存

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

	function f(x){
	  return g(x);
	}

不属于尾调用的:

	// 情况一
	function f(x){
	  let y = g(x);
	  return y;
	}
	
	// 情况二
	function f(x){
	  return g(x) + 1;
	}
	
	// 情况三
	function f(x){
	  g(x);
	}

尾调用:不一定出现在尾部,只要是最后一步操作

例子:

function f(x) {
  if (x > 0) {
    return 1;
  }
  return 2;
}

:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。(目前只有==Safari == 浏览器支持尾调用优化)

2.尾递归

一个简单的递归的例子:(这也是一个尾递归的例子)

	function add(num) {
        num = num + 10;
        console.log('num:' + num)
        if (num > 100) {
            return
        } else {
            return add(1)
        }
    }
    add(0)

不小心报错了:too much recursion 循环调用自己,陷入死循环,改正一下,这里return 的时候要把 此时的num 结果 传回去:

	function add(num) {
        num = num + 10;
        console.log('num:' + num)
        if (num > 100) {
            return
        } else {
            return add(num)
        }
    }
    add(0)

在这里插入图片描述
再来一个著著著著著著著著著著著著著著著著名的例子,递归必看:Fibonacci 数列

非尾递归的 Fibonacci 数列 实现:

	function Fibonacci(n) {
        if (n <= 1) return 1;
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
    //什么都别说了,来个100
    Fibonacci(100);

然后,没有然后了:Script terminated by timeout

尾递归优化一下:(出自 阮一峰 es6)

	function Fibonacci(n,before = 1,next = 1){
		if(n <= 1){
			return before
		}
		return Fibonacci(n-1,next,next+before);
	}
	Fibonacci(100);

然后继续100,简直秒出答案,再试试更凶残的
在这里插入图片描述
真的不能再棒了,递归救星(๑•̀ㅂ•́)و✧

ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。(死循环除外 [doge] )

3.递归函数的改写

没有改写之前计算阶乘的函数:

	function factorial(n) {
        if (n === 1) return 1;
        return n * factorial(n - 1);
    }
    console.log(factorial(5));//120

改成尾递归的写法:

	function factorial(n, total) {
        if (n === 1) return total;
        return factorial(n - 1, n * total);
    }
    console.log(factorial(10, 1));//3628800

虽然变快了,省内存了,但是,第一眼看不出来是在算阶乘

改写:

	function tailFactorial(n, total) {
        if (n === 1) return total;

        return tailFactorial(n - 1, n * total);
    }

    function factorial(n) {
        return tailFactorial(n, 1);
    }
    console.log(factorial(10));//3628800

然后这其中有个emmmm,似曾相识的概念 柯里化

	function currying(fn, n) {
	  return function (m) {
	    return fn.call(this, m, n);
	  };
	}

	function tailFactorial(n, total) {
	  if (n === 1) return total;
	  return tailFactorial(n - 1, n * total);
	}
	
	const factorial = currying(tailFactorial, 1);
	
	factorial(5) // 120

果然这个有点高大上,咱换种简单的:函数默认值

	function factorial(n,total = 1){
		if(n === 1) return total;
		return factorial(n-1,n*total);
	}

总结:循环可以用递归代替,而一旦使用递归,就最好使用尾递归

但是,问题又来了,尾递归超过一定的次数,也会发生溢出的:

	function sum(x, y) {
        if (y > 0) {
            return sum(x + 1, y - 1);
        } else {
            return x;
        }
    }

    sum(1, 1000);//too much recursion 

此时,出现了一个有意思的函数:蹦床函数,将递归执行转为循环执行

	function trampoline(f) {
	  while (f && f instanceof Function) {
	    f = f();
	  }
	  return f;
	}

做法:将原来的递归函数,改写为每一步返回另一个函数

function sum(x,y){
	if(y > 0){
		return sum.bind(null, x+1, y-1);
	}else{
		return x;
	}
}

调用的时候:

	trampoline(sum(1,100000));//100001

然鹅,这并不是真正地实现尾递归的优化:请看大佬原文

为了自己不用跳页,我还是把原文copy出来了:

	function tco(f) {
	  var value;
	  var active = false;
	  var accumulated = [];
	//accumulator 累加
	  return function accumulator() {
	    accumulated.push(arguments);
	    if (!active) {
	      active = true;
	      while (accumulated.length) {
	        value = f.apply(this, accumulated.shift());
	      }
	      active = false;
	      return value;
	    }
	  };
	}
	
	var sum = tco(function(x, y) {
	  if (y > 0) {
	    return sum(x + 1, y - 1)
	  }
	  else {
	    return x
	  }
	});
	
	sum(1, 100000)

留着研究,一时半会还没研究透,暂时看看就好

4.函数参数的尾逗号

函数的最后一个参数有尾逗号(trailing comma),允许定义和调用时,尾部直接有一个逗号

	function clownsEverywhere(
	  param1,
	  param2,
	) { /* ... */ }
	
	clownsEverywhere(
	  'foo',
	  'bar',
	);

这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。

:除了规范与避免冗余,这个暂时没有发现其他的用处,先做记录

5.Function.prototype.toString()

ES2019 之后不再省略函数中的注释,以及函数名称与 () 之间的空格,返回一模一样的原始代码

	function foo() {
	    // 这个函数的注释
	    console.log(1)
	}
	console.log(foo.toString())

打印:
在这里插入图片描述
一模一样,就是这样了。当然目前还没有发现有什么实际的用处,先记录

6.catch 命令的参数省略

其实我还没在javascript中使用过 try … catch ,但是可以先记录一下的嘛

以前

明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象

	try {
	  // ...
	} catch (err) {
	  // 处理错误
	}

ES2019:

如果没用到catch 部分,catch可以缺省

	try {
	  // ...
	} catch {
	  // ...
	}
发布了50 篇原创文章 · 获赞 4 · 访问量 1251

猜你喜欢

转载自blog.csdn.net/weixin_43910427/article/details/105418616