目录
ES6以往文章
ES6标准---【一】【学习ES6看这一篇就够了!!】-CSDN博客
ES6标准---【二】【学习ES6看这一篇就够了!!】-CSDN博客
ES6标准---【三】【学习ES6看这一篇就够了!!!】-CSDN博客
rest参数
RES6引入rest参数(形式为“...变量名”),用于获取函数或解构赋值的多余参数,这样就不需要引入“arguments”对象了
rest参数搭配的变量是一个数组,该变量将多余的参数传入数组中
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
注意事项
rest参数之后不能再有其它参数(即只能是最后一个参数),否则会报错
// 报错
function f(a, ...b, c) {
// ...
}
函数的.length属性,不包括 rest 参数
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
函数的name属性
函数的name属性,返回该函数的函数名
function foo() {}
foo.name // "foo"
如果将一个匿名函数赋值给一个变量,ES5的name属性会返回空字符串,而ES6的name属性会返回变量名:
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
但如果给这个匿名函数赋一个具体的名字,则ES5、ES6的name属性都会返回这个具名函数原本的名字
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
函数参数的尾逗号
ES6允许函数的最后一个参数有“尾逗号”
- 定义某个函数的参数时,可以有尾逗号
- 调用某个函数填写参数时,可以有尾逗号
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
Function.prototype.toString()
ES6之前的toString()方法返回函数代码本身,会省略注释和空格
function /* foo comment */ foo () {}
foo.toString()
// function foo() {}
修改后的toString()方法,明确要求返回一模一样的原始代码:
<body>
<script>
function /* 代码注释1111 */ foo () {}
console.log(foo.toString())
</script>
</body>
效果
catch命令的参数省略
ES6以前,JavaScript的“try...catch”结构,明确要求catch命令后面必须跟参数,接收try代码块抛出的错误对象
try {
// ...
} catch (err) {
// 处理错误
}
ES6中,catch语句允许省略参数
try {
// ...
} catch {
// ...
}
尾调用优化
什么是尾调用?
尾调用是函数式编程的一个重要概念,是指某个函数的最后一步是调用另一个函数(递归的特殊情况)
尾调用只在严格模式下开启,正常模式是无效的
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);
}
情况一:调用函数g之后,还有赋值操作,所以不属于尾调用
情况二:调用函数g之后,还有一个加法操作,所以不属于尾调用
情况三:等同于下面代码:
function f(x){ g(x); return undefined; }
尾调用不一定出现在函数尾部,只要是最后一步操作即可:
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
上面代码中,函数m和函数n都属于尾调用,因为它们都是函数f的最后一步调用
尾调用优化
尾调用的特殊点是:“它特殊的调用位置”
函数调用会在内存形成一个“调用记录”,又称“调用帧”,保存调用位置和内部变量等信息
如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧
等到函数B运行结束,将结果返回给A,A的调用帧才会消失
如果函数B内部还调用函数C,那就还有一个C的调用帧
所有的调用帧共同组成了“调用栈”(call back)
- 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用不到了,只要内层函数的调用帧,取代外层函数的调用帧即可
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
只保留了g(3)的调用帧,这就叫做“尾调用优化”
注意:
- 只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”
function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
上述代码就没有办法进去“尾调用优化”,因为内层函数inner调用了外层函数的内部变量one
尾递归
函数调用自身,称为递归,如果尾调用自身,就称为尾递归
地柜非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”
但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”
由一般求阶乘的递归改为尾递归求阶乘:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
改为:
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
递归函数的改写
实现尾递归有两种方法
- 可以把所有用到的内部变量改写成函数的参数
- 利用函数默认值
方法一:
function tailFactorial(n, total) {
if (n === 1) return total;
return tailFactorial(n - 1, n * total);
}
function factorial(n) {
return tailFactorial(n, 1);
}
factorial(5) // 120
方法二:
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120