ES6(六: 函数扩展)(默认值,rest参数,扩展运算符)

前言

想必在ES6没出现类语法之前,js中最重要的类型就是函数。无可争议,这就是一个大重点。

(一)函数参数的默认值

在ES6之前,我们的做法无非这几种

var func = function(a,b){
     // 开 关 写法
    var a = a || '默认a';
     // 判 断 写法
    if (typeof b === 'undefined') b = '默认b';
    console.log(a+' and '+b);
};

func(); // 默认a and 默认b
func('赋值a'); // 赋值a and 默认b
func('赋值a',null); // 赋值a and null
func(null,'赋值b'); // 默认a and 赋值b

上面代码,a采用开关方式赋予初始化值(最常用方式),b采用判断方式。函数形参没传入实参就是undefined,但是注意null !== ‘undefined’。

还有一种方式,用过arguments的length属性,这也是重载的实现方案(ps: js不支持java那种方法重载)。

var func = function(){
    // arguments写法
    if (arguments.length === 1) {
        y = '默认b';
    }
}

(1)ES6允许为函数的参数设置默认值

即直接写在参数定义的后面。我们将上面例子改成ES6的版本。

const func = (a = '默认a',b = '默认b') =>{
    console.log(a+' and '+b);
};

func(); // 默认a and 默认b
func('赋值a'); // 赋值a and 默认b
func('赋值a',null); // 赋值a and null
func(null,'赋值b'); // null and 赋值b

这里和上面第四句输入有点差别。

这是因为,定义了默认值的参数,应该是函数的尾参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。而且判断默认值是使用 undefined(所以建议默认参数都设置在尾部)。

我们看下babel的编译就知道为什么了。

var func = function func() {
    var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '默认a';
    var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '默认b';

    console.log(a + ' and ' + b);
};

(2)与解构赋值默认值结合

常见用法中是配合解构赋值的默认值使用。使语法更加简洁直观。

举个例子

const fetch= (url, { body = '', method = 'GET', headers = {} }) =>{
    console.log(method);
};
// GET
fetch('http://example.com', {});
// POST
fetch('http://example.com', {method: 'POST'});
// TypeError: Cannot match against 'undefined' or 'null'.
fetch('http://example.com');

上面代码,第一次调用,传入空对象,找不到method 默认值生效。第二次调用,传入method 变成传入值。但是第三次调用,因为第二个参数没传,就是undefined,导致解构不成立,所以报错。

这时就出现了双重默认值

const fetch= (url, { body = '', method = 'GET', headers = {} } = {}) =>{
    console.log(method);
};

上面代码,函数fetch 没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method 才会取到默认值GET 。

(3)函数的length属性

函数的length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后, length 属性将失真。

console.log((function(a,b){}).length); // 2
console.log((function(a = 5){}).length); // 0
console.log((function(a, b, c = 5){}).length); // 2
console.log((function(...args){}).length); // 0

上面代码中, length 属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。

比如,上面倒数第二个函数,定义了3个参数,其中有一个参数c 指定了默认值,因此length 属性等于3减去1,最后得到2。

这是因为length 属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。

当然,最后一个 rest参数也不会计入length 属性。

(4)作用域

大家都知道,变量的作用域规则是,即先是当前函数的作用域,然后才是全局作用域。

如果参数默认值是一个变量,则该变量所处的函数作用域内,而不是全局作用域。

const x = 1;
const f = (x, y = x) => {
    console.log(y);
};

f(2); // 2

参数y 的默认值等于x 。调用时,由于函数作用域内部的变量x 已经生成,所以y 等于参数x ,而不是全局变量x 。

当调用时,函数作用域内部的变量x 没有生成,结果就会不一样。

const func = (y = x) =>{
    let x = 2;
    console.log(y);
};
func(); //1

上面代码中,函数调用时, y 的默认值变量x 尚未在函数内部生成,所以x 指向全局变量,结果又不一样。

注意,此时,如果全局变量x 不存在,就会报错( ReferenceError: x is not defined)。

如果函数A 的参数默认值是函数B

由于函数的作用域是其声明时所在的作用域,那么函数B 的作用域不是函数A ,而是全局作用域。

let foo = 'outer';
function bar(func = x => foo) {
    let foo = 'inner';
    console.log(func()); // outer
}
bar();

函数bar 的参数func ,默认是一个匿名函数,返回值为变量foo 。这个匿名函数的作用域就不是bar 。

这个匿名函数声明时,是处在外层作用域,所以内部的foo 指向函数体外的声明,输出outer 。它实际上等同于下面的代码。

let foo = 'outer';
let f = x => foo;
function bar(func = f) {
    let foo = 'inner';
    console.log(func()); // outer
}
bar();

(二)rest 参数

ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。类似java的不定项参数(只能是方法的最后一个参数)。

rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

const  func = (value1, ...values) => {
    let sum = 0;
    for (var val of values) {
        sum += val;
    }
    console.log(`value1= ${value1}`); // value1= first
    console.log(`sum= ${sum}`); // sum= 6
};

func('first',1,2,3);

上面代码的函数,利用rest参数,可以向该函数传入任意数目的参数。
注意:rest参数必须在最后面。否则会报错。

// SyntaxError: Rest parameter must be last formal parameter

(三)扩展运算符

扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。主要用于函数调用(react中展开数组)

function push(array, ...items) {
    array.push(...items);
}

function add(x, y) {
    return x + y;
}

var numbers = [4, 38];
console.log(add(...numbers)); // 42

应用(1)替代数组的apply方法

由于扩展运算符可以展开数组,所以不再需要apply 方法,将数组转为函数的参数了。

// ES5的写法
function func(x, y, z) {
    console.log(`${x}-${y}-${z}`);
}
var args = [0, 1, 2];
// null undefined指向window
func.apply(null, args); // 0-1-2

// ES6的写法
const func1 = (x, y, z)=> {
    console.log(`${x}-${y}-${z};`);
};
let args1 = [0, 1, 2];
func1(...args1); // 0-1-2;

上面代码是一个spread 替代的例子。这样我们也可以应用Math.max 和Math.min方法。

var arr = [14, 3, 77];

// ES5的写法
console.log(Math.max.apply(null, arr)); // 77
console.log(Math.min.apply(null, arr)); // 3

// ES6的写法
console.log(Math.max(...arr)); // 77
console.log(Math.min(...arr)); // 3

上面代码表示,由于JavaScript不提供求数组最大最小元素的函数,所以只能套用Math.max 函数,将数组转为一个参数序列,然后求最大值。

应用(2)将一个数组添加到另一个数组尾部

// ES5的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [ 0, 1, 2, 3, 4, 5 ]

// ES6的写法
const arr3 = [0, 1, 2];
const arr4 = [3, 4, 5];
arr3.push(...arr4);
console.log(arr3); // [ 0, 1, 2, 3, 4, 5 ]

上面代码的ES5写法中, push 方法的参数不能是数组,所以只好通过apply 方法将数组变成单个使用push 方法。
有了扩展运算符,就可以直接将数组传入push 方法。

下面是一个自定义时间节点。

// ES5
var data = new (Date.bind.apply(Date, [null, 2017, 10, 1]));
console.log(data); // 2017-10-31T16:00:00.000Z

// ES6
const data1 = new Date(...[2017, 10, 1]);
console.log(data1); // 2017-10-31T16:00:00.000Z

应用(3)合并数组

扩展运算符提供了数组合并的新写法。

var arr1 = ['a', 'b'];
var arr2 = ['c'];

// ES5
var newArr = arr1.concat(arr2);
console.log(newArr); // [ 'a', 'b', 'c' ]

// ES6
const newArr1 = [...arr1, ...arr2];
console.log(newArr1); // [ 'a', 'b', 'c' ]

应用(4)与解构赋值结合

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

const [first1, ...rest1] = [];
console.log(first1); // undefined
console.log(rest1); // []

const [first2, ...rest2] = ["foo"];
console.log(first2);// "foo"
console.log(rest2); // []

上面代码,将扩展运算符用于数组赋值。但是需要注意,这里rest参数也只能放在最后一位,不然会报错。

应用(5)函数返回值

JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

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

我尝试将readDateFields写成一个数组,然后模拟书上的API的返回值。

const dateFields = [2017, 10, 1];
const d = new Date(...dateFields);
console.log(d); // 2017-10-31T16:00:00.000Z

应用(6)字符串

扩展运算符还可以将字符串转为真正的数组。

var str = "hello";
// ES5 的处理方式
var chars=str.split("");
console.log(chars);// [ 'h', 'e', 'l', 'l', 'o' ]

// ES6 的处理方式
const b = [...str];
console.log(b); // [ "h", "e", "l", "l", "o" ]

应用(7)类数组对象

任何类似数组的对象,都可以用扩展运算符转为真正的数组。

var nodeList = document.querySelectorAll('p');
var arr = [...nodeList];
console.log(nodeList);
console.log('------------');
console.log(arr);

运行结果为:
ES6(六: 函数扩展)(默认值,rest参数,扩展运算符)_第1张图片

上面截图可以看出,扩展运算符将类数组对象(NodeList),转化为了真正的数组(Array);

应用(8)Map,Set,Generator

扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。

let map = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three']
]);
let arr = [...map.keys()];
console.log(arr);  // [ 1, 2, 3 ]

Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

var go = function*(){
    yield 1;
    yield 2;
    yield 3;
};
const arr1 = [...go()];
console.log(arr1); // [1, 2, 3]

上面代码中,变量go 是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。

注意:

如果对没有iterator 接口的对象,使用扩展运算符,将会报错。

// TypeError: Cannot spread non-iterable object

你可能感兴趣的:(ECMAScript6)