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 {
// ...
}