ECMAScript6 新特性——“函数的扩展”

1 函数参数的默认值

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面:

function log(x = "message.",y = "duration infomation.") {
    console.log(x, y);
}

log(); //message. duration infomation.
log("hello world.", "connecting."); //hello world. connecting.

参数变量是默认声明的,所以不能用let和const再次声明

function add(x = 0, y = 0) {
    let x; //报错
    const y; //报错
    console.log(x + y);
}
add();

与结构赋值默认值结合使用

function add({x = 0, y = 0}) {
    console.log(x + y);
}
add({}); //0
add({y: 1}); //1

//当add()时,则会报错
add(); //报错

//如何才能在add()时不报错呢?
//这就需要用到双重默认值,即默认为函数传入一个对象
function add({x = 0, y = 0} = {}) {
    console.log(x + y);
}
add({}); //0
add({y: 1}); //1
add(); //0

如下:

function add({name = "your name here", age = "your age here"} = {}) {
    console.log(`${name}, ${age}`);
}
add(); //your name here, your age here
add({name: "Oliver"}); //Oliver, your age here
add({name: "Troy", age: 18}); //Troy, 18

函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。

function add(x,y = 0) {
    console.log(x + y);
}
console.log(add.length); //1 函数预期传入1个参数,因为有一个参数已经有默认值了

作用域

如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。

var x = 10;
function log(x, y = x) { //x已经在内部生成,使用的将是内部的x
    console.log(y);
}
log(2); //2

var x = 10;
function log(y = x) { //x不存在,使用的将是外部的x
    console.log(y);
}
log(); //10

实际应用

省略参数抛出错误:

function throwIfMissing() {
    throw new Error("Missing parameter.");
}
function foo(x = throwIfMissing()) {
    return x;
}
console.log(foo(10)); //10
console.log(foo()); //Uncaught Error: Missing parameter.

2 rest参数

rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了

function foo(...values) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(sum);
}
foo(1,2,3);

注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

function foo(x, ...values) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(x, sum);
}
foo(1,2,3); //x是1,sum是2+3

function foo(x, ...values, y) {
    let sum = 0;
    for(let val of values) sum += val;
    console.log(x, sum);
}
foo(1,2,3); //Rest parameter must be last formal parameter

3 扩展运算符

扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

let a = [1,2,3,4,5,6,7];
console.log(...a); //1 2 3 4 5 6 7
a.push(...a);
console.log(a); //[1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]

该运算符主要用于函数的调用

let a = [1,2,3,4,5,6,7];
function add(...args) {
    let sum = 0;
    for (let val of args) console.log(sum += val);
    console.log(`done. final sum: ${sum}`);
}
add(...a); //依次传入a数列中的值
//1
//3
//6
//10
//15
//21
//28
//done. final sum: 28

替代数组的apply方法

扩展运算符可以展开数组,所以不再需要apply方法

//ES5
let a = [1,2,3,4,5,6,7];
console.log(Math.max.apply(null,a)); //7
//ES6
console.log(Math.max(...a)); //7

又比如push函数

let a = [];
let num = [1,2,3,4,5,6,7];
a.push(...num);
console.log(a.toString()); //1,2,3,4,5,6

扩展运算符的应用

合并数组

let a = [];
let a1 = [1,2,3];
let a2 = [4,5];
let a3 = [6,7,8,9,10];
a = [...a1, ...a2, ...a3];
console.log(a.toString()); //1,2,3,4,5,6,7,8,9,10

与解构赋值结合

let [...rest] = [1,2,3,4,5,6];
console.log(rest); //[1,2,3,4,5,6]

函数的返回值

var dateFields = readDateFields(database);
var d = new Date(...dateFields);

字符串

console.log([...'hello']); //["h", "e", "l", "l", "o"]

扩展运算符能够识别32位Unicode字符

可以正确返回字符串长度:

console.log([...'hello']); //["h", "e", "l", "l", "o"]
function len(...args) {
    for (let val of args) console.log([...val].length);
}
len("hello", "another one 哈哈"); //14

类似数组的对象

var nodeList = document.getElementsByTagName("p");
var array = [...nodeList];

含有Iterator接口对象

只要具有Iterator接口的对象,都可以使用扩展运算符

Map,Set,Generator等

4 name属性

函数的name属性,返回该函数的函数名。

如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而ES6的name属性会返回实际的函数名。

var x = function() {};
console.log(x.name); //"" ES5返回空字符串,ES6返回x

将一个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字。

var x = function y() {};
console.log(x.name); //"y"

Function构造函数返回的函数实例,name属性的值为“anonymous”。

console.log((new Function).name); //anonymous

bind返回的函数,name属性值会加上“bound ”前缀。

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

5 箭头函数

ES6允许使用“箭头”(=>)定义函数。

let add = (x,y) => x + y;
console.log(add(1,2)); //3

function add(x,y) {
    return x + y;
}
console.log(add(1,2)); //3

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

let add = (x, y) => {
    console.log("done");
    return x + y;
};
console.log(add(1, 2)); //3

如果箭头函数直接返回一个对象,必须在对象外面加上括号。

let add = (x, y) => ({ result: x + y });
console.log(add(1, 2).result); //3

箭头函数可以与变量解构结合使用。

let add = ({ name, age }) => ({ result: `${name},${age}` });
console.log(add({ name: "Oliver", age: 18 }).result); //Oliver,18

箭头函数的一个用处是简化回调函数

var arr = [1, 2, 3, 4, 5];
arr.map(function(item, index, array) {
    console.log(item);
});

arr.map((item, index, array) => { console.log(item); });

//上面两种函数写法功能相同,下面的明显较为简洁

使用注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。

6 ES7函数绑定

ES7中函数绑定运算符(::)作用是代替call、apply、bind调用。

7 尾调用优化

尾调用

尾调用(Tail Call)就是指某个函数的最后一步是调用另一个函数:

function g() {
    console.log("done.");
}

function j() {
    console.log("almost done.");
}

function log(x) {
    if (x > 0) {
        return g();
    }
    return j();
}
log(10); //done.
log(0); //almost done.

g()和j()的调用都属于尾调用,因为都是函数的最后一步

尾调用优化

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。


function addOne(a) {
    var one = 1;

    function inner(b) {
        return b + one;
    }
    return inner(a);
}

尾递归

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

所以确保最后只调用自身就可以了,做法是将所有参数传入要调用的函数中去:

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

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

8 ES7函数参数的尾逗号

ES7提案允许函数的最后一个参数有尾逗号。

你可能感兴趣的:(es6,javascript)