ES6 函数的扩展

函数的扩展我们可以从三个方面来了解与认识:

  • 参数的默认值
  • 箭头函数
  • 关于尾调用的优化

参数的默认值

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);
}

函数 f1f2 都属于尾调用,因为这两个函数调用都是函数 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();
};

更多链接:https://www.9xkd.com/