【译】快速入门ES6解构以及rest参数语法

原文:https://gist.github.com/yang-wei/3d35e8692dbc6cc0f98d

写在前面

我们首先会讨论如何在ES6中对数组以及对象使用解构(destructing)和rest参数语法。然后我们接下来再看一些例子,并且讨论一些quiz。

数组

1
2
3
4
5
6
7
8
9
10
var array = [1, 2, 3, 4];
var nestedArray = [1, 2, 3, 4, [7, 8, 9]];

var [a, b, c, d] = array;
console.log(a, b, c, d)
// -------- 1 2 3 4

var [a, , , d, [x, y, z]] = nestedArray;
console.log(a, d, x, y, z)
// -------- 1 4 7 8 9

使用rest参数语法,可以省略参数的个数:

1
2
3
4
5
6
7
8
var [a, b, c] = array;
console.log(a, b, c)
// -------- 1 2 3

// rest parameter
var [a, b, ...c] = array;
console.log(c);
// [3, 4]

然而当使用rest参数的时候,你必须将它放置在最后一个位置:

1
2
var [...head, d] = array;
// Uncaught SyntaxError: Unexpected token...

当解构的参数个数超过了数组中元素个数的时候,多出来的参数的值会是undefined:

1
2
3
var [a, b, c, d, e] = array;
console.log(e);
// undefined

但是我们可以对参数设置默认值,这样就不用担心出现undefined啦:

1
2
3
var [a, b, c, d, e = 5] = array;
console.log(e);
// -------- 5

我们可以轻而易举地拷贝一个数组:

1
2
3
4
5
6
7
8
var [...clonedArray] = array;
console.log(clonedArray);
// [1, 2, 3, 4]

// 拷贝一个嵌套数组
var [,,,, [...clonedNestedArray]] = nestedArray;
console.log(clonedNestedArray);
// [7, 8, 9]

还可以轻而易举地交换元素的值:

1
2
var a = 1, b = 2;
[b, a] = [a, b];

这是早先JavaScript获取函数入参的代码:

1
2
3
4
5
function foo() {
return Array.prototype.slice.call(arguments, 0);
}

foo(1,2,3,4,5) // [1, 2, 3, 4, 5]

现在我们可以将它重构成更精简的代码:

1
2
3
4
5
function foo(...args) {
return args;
}

foo(1,2,3,4,5) // [1, 2, 3, 4, 5]

对象

1
2
3
4
5
6
7
8
9
10
var object = {a: "A", b: "B", c: "C"};
var nestedObject = {a: "A", b: "B", c: "C", x: {y: "Y", z: "Z"}};

var {a: A, b: B, c: C} = object;
console.log(A, B, C);
// ------ "A" "B" "C"

var {a: A, b: B, c: C, x: X} = nestedObject;
console.log(X);
// {y: "Y", z: "Z"}

如果我们仅需要对象中的一个字段的话:

1
2
3
var {b: B} = object;
console.log(B);
// ------- "B"

与数组类似,我们同样可以对参数设置默认值:

1
2
3
4
5
6
7
8
var {b: B, d: D = "D"} = object;
console.log(B, D);
// ------- "B" "D"

// 如果对象中没有对应的key的话,将会返回undefined
var {a: A, b: B, d: D} = object;
console.log(A, B, D);
// ------- "A" "B" undefined

如果我们每次都要显式地书写{ keys: newVariable },那未免过于啰嗦了。所以我们可以采用简写的形式:

1
2
3
var {a, b, c} = object;
console.log(a, b, c);
// -------"A" "B" "C"

乍一看可能会有些困惑,其实上面这段代码本质上等同于:

1
2
var {a: a, b: b, c: c} = object;
// 新的a, b, c 将会被创建

显然,如果你想取不同的参数名,那么就无法使用简写形式。
让我们继续看一下其他例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var object = {a: "A", b: "B", c: "C"};
var nestedObject = {a: "A", b: "B", c: "C", x: {y: "Y", z: "Z"}};


var {a, b, c, x} = nestedObject;
console.log(x);
// { y: "Y", z: "Z" }


var {b} = object;
console.log(b);
// ------- "B"

var {b, d = "D"} = object;
console.log(d, d);
// ------- "B" "D"

// 如果对象中没有对应的key的话,将会返回undefined
var {a, b, d} = object;
console.log(a, b, d);
// ------- "A" "B" undefined

在解构对象的时候,同时使用rest参数语法可能会导致失败:

1
2
3
4
5
6
7
8
// error
// var {a: A, b: B, c: ...C} = object;
// console.log(A, B, C); <-- error
// use the shorthand method

var {a, b, ...c} = object; // es7
console.log(c);
// {c: "C"}

接下来展示一下当使用对象解构语法,我们对代码可以做到怎样的精简:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var John = {name: "John", age: 20};
var Marry = {name: "Marry"};

function getAge(person) {
age = person.age || 18 // 默认值
return age
}
getAge(John); // 20
getAge(Marry); // 18

// with es6
function getAge({age = 18}) {
return age
}

举例

让我们看一下在实际编码中,解构以及rest参数语法的使用场景。首先我们看一下函数的可变参数实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function sum(...numbers) {
return numbers.reduce((n, total) => {
return n + total
}, 0);
}
sum(); // 0
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15

// 一个更为抽象的例子(稍微有点偏题,😁)
function math(equation, ...numbers) {
return numbers.reduce((n, total) => {
return equation.call(null, n, total);
});
}

const add = (a, b) => { return a + b; }
let sum1 = math(add, 1, 2, 3, 4);
// 10

const times = (a, b) => { return a * b; }
let product1 = math(times, 1, 2, 3)
// 6

Redux

让我们在redux中寻找一些例子。在redux源码中,存在一个util方法 - compose,它常被用来简化代码:

1
function (arg) { return fn1(fn2(fn3(arg))); }

使用compose:

1
function(arg) { compose(fn1, fn2, fn3)(arg) }

让我们看一下compose实际做了啥,这是一个精简的源码解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function compose(...funcs) {   // 接受一组函数作为入参,比如 fn1, [fn2, fn3...]
return (...args) => { // 返回一个函数,该函数的入参比如 arg1, [arg2, arg3... ]
// 由于使用了rest参数语法,此时args为一个数组: args = [arg1, arg2, ...]
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)

return rest.reduceRight((composed, f) => f(composed), last(...args))
// (composed, f) => f(composed)没什么好说的,相当于实现函数的链式调用
// 我们主要解释last(...args)这部分
// 因为我们的函数接受的入参是last(arg1, arg2...),而不是last([arg1, arg2...])
// 所以我们需要确保[arg1, arg2, ...] 转变成 arg1, arg2, ...
// 那么我们可以使用rest参数语法
}
}

让我们继续看一个例子,来理解上述的args到底是如何被转变的:

1
2
3
4
5
6
7
8
function inspect(...args) {
// 当此函数被调用时
// args = ["a", "b", "c"]

console.log(args) // ["a", "b", "c"]
console.log(...args) // "a" "b" "c"
}
inspect("a", "b", "c")

Quiz

ES6 JavaScript quiz 中有一些有趣的关于解构和rest参数的quiz,让我们来看看它们吧~

Question 3

1
let x, { x: y = 1 } = { x }; y;

让我们来逐步解析:

1
2
3
4
5
6
7
let x, // x = undefined
{ x: y = 1 } = { x }
// 在这里y被设置了一个默认值1
// 又由于右边的x此时是undefined状态
// 所以上述代码等同于:
// { x: y = 1 } = { }
y; // 所以y返回1

Question 7

1
[...[...'...']].length

一开始我被这段代码给吓到了。但是 '...' 本质上只是一个字符串。让我们简化一下代码:

1
[...[...'str']]

由于字符串是可遍历的,所以 ...'str' 将返回 ["s", "t", "r"] 。然后又因为 [...["s", "t", "r"]] 将返回 ["s", "t", "r"] 。所以答案很明显,那就是3啦~

Question 11

1
((...x, xs)=>x)(1,2,3)

在数组那节中已经提到过了,rest参数只能放在最后一个位置,所以上述代码会抛出一个错误。

总结

这就是全部我要分享的内容啦~希望你能通过本文快速地掌握解构以及rest参数语法的基础使用方式,Have fun!

参考资料

Young wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作