函数的扩展我们可以从三个方面来了解与认识:
- 参数的默认值
- 箭头函数
- 关于尾调用的优化
参数的默认值
在 ES6
之前,我们不能直接为函数的参数指定默认值,只能采用变通的方法。而 ES6
中允许我们直接为函数的参数设置默认值,通过等号 =
直接在小括号中给参数赋值。
示例:
function log(name, age = 18) {
console.log(name, age);
}
console.log('xkd'); // 输出: xkd
console.log('summer', 20); // 输出: summer 20
console.log('mark', ''); // 输出: mark
如果函数带有某一个参数,则表示这个参数变量已经默认声明,我们不可以使用 let
或者 const
再次声明这个变量。
function xkd(a = 1) {
let a = 2;
}
// 报错:SyntaxError: Identifier 'a' has already been declared
还需要注意的是,在使用函数默认参数时,不允许有同名参数:
function week(a, a, b = 100) {
/* ... */
}
// 报错信息: SyntaxError: Duplicate parameter name not allowed in this context
参数默认值是惰性求值:
let a = 100;
function xkd(sum = a + 1) {
console.log(sum);
}
xkd(); // 输出:101
rest不定参数
ES6
引入 rest
参数,不定参数用来表示不确定参数个数,由 ...
加上一个具名参数标识符组成。
示例:
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
console.log(add(10, 5, 7)); // 输出:22
上面代码的 add
函数是一个求和函数,利用 rest
参数,可以向该函数传入任意数目的参数。
注意 rest
参数只能放在参数组的最后,并且有且只有一个不定参数,这个参数后面不能再有其他参数,否则会报错。
function func(a, ...b, c) {
// ...
}
// 报错:SyntaxError: Rest parameter must be last formal parameter
如果我们要使用函数的 length
属性来获取参数的个数,是不包括 rest
参数的。
function func1(a, b){
//...
}
function func2(a, b, ...c){
//...
}
console.log(func1.length); // 输出: 2
console.log(func2.length); // 输出: 2
name属性
函数的 name
属性,可以用于返回该函数的函数名。
示例:
function xkd() {
console.log(xkd.name); // 输出:xkd
}
xkd();
也可以写成:
var xkd = function() {}
console.log(xkd.name); // 输出:xkd
箭头函数
ES6
中允许使用箭头 =>
来定义函数,这样定义的函数叫做箭头函数。箭头函数主要的两点是:它的 this
指向是在它当时定义所在的区域,还一个是它没有一个作用域的提升。
示例:
var x = function (y) {
return y;
}
上述函数使用箭头函数来写,可以写成:
var x = y => y;
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
示例:
例如下面这个求两数之和的函数:
var sum = function(num1, num2) {
return num1 + num2;
}
等同于如下所示箭头函数的写法:
var sum = (num1, num2) => num1 + num2;
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回。
示例:
var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
示例:
let obj = age => ({ name: "xkd", age:age });
箭头函数可以与变量解构结合使用。
示例:
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
箭头函数使用时需要注意的一下几个问题:
- 函数体内的
this
对象指向的是定义时所在的对象,而不是使用时所在的对象。 - 不可以当作构造函数,也就是说不可以使用
new
命令。 - 不可以使用
arguments
对象,可以使用rest
参数替代。 - 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数。
关于尾调用的优化
尾调用就是指某个函数的最后一步是调用另一个函数,不适用于外部变量时只保留内层函数的调用帧。
示例:
例如下面这个 func()
函数中的最后一步是调用函数 show()
,这就叫尾调用:
function func(x) {
return show(x);
}
如果在调用一个函数之后,还有其他的操作,则不属于尾调用。
注意,尾调用不一定出现在函数尾部,只要是最后一步操作即可,例如下面这段代码中:
function func(x) {
if (x > 0) {
return f1(x)
}
return f2(x);
}
函数 f1
和 f2
都属于尾调用,因为这两个函数调用都是函数 func
的最后一步操作。
在 ES5
中,尾调用的实现是创建一个新的栈帧 stack frame
, 将其推入调用栈中表示函数调用。所以每一个未用完的栈帧都会保存在内存中,当调用栈变得过大时会造成程序问题,也就是我们常说的栈溢出。
ES6
严格模式下,满足以下三个条件,尾调用不再创建新的 stack frame
,而是重用当前 stack frame
:
- 函数不是一个闭包。
- 在函数内部,尾调用是最后一条语句。
- 尾调用的结果作为函数值返回。
示例:
'use strict';
function func(){
//优化后
return f();
}
而以下几种情况不会进行优化:
- 当没有作为函数值返回,不会优化:
function func(){
f();
}
- 返回值后还需要做其他操作,也不会优化:
function func(){
return 1+f(); //返回值后还需要做其他操作
};
- 函数调用不在尾部,也不会优化:
function func(){
const result = f();
return result;
};
- 调用的函数是一个闭包函数,也不会优化:
function func(){
const a= 1;
const f = () => a //这是一个闭包函数,函数 f 需要访问局部变量a
return f();
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。