ES6 引入了一个新的操作符 ...
,根据它的使用情况决定是展开或者折叠操作数。
展开/折叠
让我们看下面这段代码
1 | function foo(x,y,z) { |
当 ...
操作符出现在数组前面,它表现为将数组展开成一个个的数。
这种使用方式最典型的情况就是将数组展开成函数的参数。这种使用方式和 apply
类似:
1 | foo.apply( null, [1,2,3] ); // 1 2 3 |
同时 ...
还可以用作将数组展开到别的上下文中,例如展开到别的数组当中:
1 | var a = [2,3,4]; |
这种使用方式与 [1].concat(a,[5])
一样。
另外一种使用方式,与上面的情况相反,它是将多个数值折叠到一个变量当中,变成一个数组。如下所示:
1 | function foo(x, y, ...z) { |
...z
的意思是将除了x,y
两个之外的参数聚合到数组 z
当中来。 注意这里与第一个例子的区别,展开是函数调用时,将传入的数组参数展开;折叠是函数声明时,将调用可能传入的其他参数聚合到变量中。
所以上面这个例子是 x
赋值为 1
,y
赋值为 2
, 其他的 3,4,5
聚合到数组 z
中来。
当然如果没有其他任何命名的参数, ...
会把所有参数折叠成一个数组:
1 | function foo(...args) { |
...args
一般被叫做剩余参数(rest parameters),因为你可以收集剩余的参数,或者说聚集,折叠这些参数。
这种使用方式是JS中以前的类数组参数 arguments
的一个有效的替代。
看一个例子:
1 | // doing things the new ES6 way |
默认参数
相信下面这种默认参数的使用方式,你肯定不陌生:
1 | function foo(x,y) { |
但是使用上面的代码,你会遇到一些问题:
1 | foo( 0, 42 ); // 53 <-- Oops, not 42 |
为了修复这个问题,可以做一个更加细致的检查
1 | function foo(x,y) { |
下面我们看看ES6中加入的默认参数值的语法:1
2
3
4
5
6
7
8
9
10
11
12
13
14function foo(x = 11, y = 31) {
console.log( x + y );
}
foo(); // 42
foo( 5, 6 ); // 11
foo( 0, 42 ); // 42
foo( 5 ); // 36
foo( 5, undefined ); // 36 <-- `undefined` is missing
foo( 5, null ); // 5 <-- null coerces to `0`
foo( undefined, 6 ); // 17 <-- `undefined` is missing
foo( null, 6 ); // 6 <-- null coerces to `0`
注意上面这些函数参数中细微的差别导致的结果的不同。
x=11
比起 x||11
更像是 x !== undefined ? x : 11
,所以在使用的过程中要注意这一点。
默认表达式值
函数默认参数值不仅仅是一些简单的字面量,而且还可以是一个有效的表达式,甚至是一个函数调用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function bar(val) {
console.log( "bar called!" );
return y + val;
}
function foo(x = y + 3, z = bar( x )) {
console.log( x, z );
}
var y = 5;
foo(); // "bar called"
// 8 13
foo( 10 ); // "bar called"
// 10 15
y = 6;
foo( undefined, 10 ); // 9 10
可以看到这些参数值会在需要的时候进行计算得到,什么叫需要的时候?就是传入的参数省略,或者是 undedined
的时候
要注意到一个细节,函数声明中的参数是在它们自己的作用域而不是函数体的作用域,也就是说,函数参数表达式的变量,首先匹配前面的变量,然后再匹配外部变量。如下的情况:1
2
3
4
5
6
7var w = 1, z = 2;
function foo( x = w + 1, y = x + 1, z = z + 1 ) {
console.log( x, y, z );
}
foo(); // ReferenceError
w+1
中的 w
首先在括号内找 w
,但是没找到,于是它去括号外面找。而 x+1
中的 x
已经在括号内找到,就不要到括号外面去找。
z+1
中的z
在括号内找到了,但是呢没有被初始化。于是就会引起一个错误。
解构赋值
ES6引入了一个语法特性叫做解构赋值,看下面的代码
1 | function foo() { |
我们把 foo()
函数返回的结果数组分别赋值给变量 a
, b
, c
。这种赋值叫做结构化赋值(structured assignment)
还有一种对象结构化赋值:
1 | function bar() { |
我们看看解构赋值的形式:
1 | var [ a, b, c ] = foo(); |
对象属性赋值模式
让我们深入挖掘一下之前的 {x:x,...}
语法,如果属性名和你想要赋值的属性名一致,则可以写得更加简洁:
1 | var { x, y, z } = bar(); |
有时候我们希望赋值给不同的变量,那么可以使用一种冗长的形式:
1 | var { x: bam, y: baz, z: bap } = bar(); |
要注意上面的一个细节,冒号左边是值,右边的要赋值的变量。
不仅仅是声明
解构是一个一般性的赋值操作,不仅仅是一个声明:
1 | var a, b, c, x, y, z; |
在上面的例子中,变量已经被声明了,我们做的是赋值。
配合...
解构赋值还可以和 ...
操作符结合:
1 | var a = [2,3,4]; |
这里 ...
可以将数组解构,在ES6中并不能用于对象的解构
配合默认值
解构也可以提供默认值:
1 | var [ a = 3, b = 6, c = 9, d = 12 ] = foo(); |
对象字面量表达式
ES6对对象字面量增加了一些扩展。 如果属性名和变量名一样,可以像上面一样使用简洁的赋值方式:
1 | var x = 2, y = 3, |
如果属性值是函数,可以简单的这么定义:
1 | var o = { |
总结
本文介绍了ES6中的 ...
与解构赋值。 ...
在不同情况下有不同的表现,有时是折叠操作数,有时是展开操作数。在函数声明的时候是聚集传过来的操作数,在函数调用的时候是将数组展开为参数传入函数。