想必在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();
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);
上面截图可以看出,扩展运算符将类数组对象(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