Javascript6/ES2015中的解构详解

  ECMAScript 2015引入了许多语法糖帮助你编写更精确代码,解构(destructuring)是最引人注目之一。首先我们看看什么是解构?

解构是用来分配表达式充实到复杂对象或数组等数据结构中,你可以通过文字符号来发现是否使用了解构,比如对于对象使用了一对括号{};而对于数组使用[],这些符号中的分配值都是从左到右。

 

//注意下面等号之间的方括号 [ 和 ] 表示这是进行解构分配
var [x, y] = z;

//这是对象的结构分配
var { a, b } = c;

// 与正常赋值分配混合在一起使用
var { a, b } = c,
x = 12 + 1;

// 注意 shadowing!
var { a, b } = c,
x = 12 + 1,
[x, y] = [1, 2];

// x值是 1.

 

数组解构分配

通常一个数组的写法如下:

[1, 2, 3] // 一个数组符号.

解构分配是试图从左边获得名称,逐个向右直至到达数组最右边,如果分配赋值等号两边个数不是一样的,左边个数大于右边,那么右边值从左边的第一个开始填入赋值,左边多余的就被填入undefined;如果左边个数小于右边个数,那么也是从左边第一个开始填入赋值,右边多余的忽视。

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

这里左边是两个元素[a,b],而右边是三个元素,右边比左边多:

// [1, 2, 3]
// | | |
// [a, b] 忽视

[1,2,3]是三个元素,前面两个赋值分配给了[a,b],第三个元素“3”就忽视了

结果就是两个值:

1. a 是数组的第一个元素,其值是1

2. b是数组的第二个元素,其值是2

简写如下:

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

如果左边超过右边的长度,那么左边超出部分将被赋值为undefined;

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

// a = 1
// b = 2
// c = undefined

在实践中,数组解构赋值并没有对象解构赋值有用。

对象解构分配

对象解构分配遵循同样的想法,但是因为对象是更加复杂,它因此也复杂一些,在操作对象上更有实践意义。

对象解构分配虽然也是遵循从左到右的匹配方式,但是对象能嵌套,key能够命名指定:

var { a, b } = { a: 2, b: 3 };

// { a, b }
// | |
// { a: 2, b: 3 }

结果是,a等于2而b等于3,更加精确写法:

var a = { a: 2, b: 3 }.a;
var b = { a: 2, b: 3 }.b;

你可以不必按照名称作为key,只要明确指定名称即可:

var { a: x, b: y, c: z } = { a: 2, b: 3 };

// x = 2
// y = 3
// z = undefined

进行如下嵌套分配赋值:

var { a: { b } } = { a: { b: 42 } };

// b = 42
// Notice a is not here!
// To have a too:

var { a, a: { b } } = { a: { b: 42 } };
// a = { b: 42 }
// b = 42

你能混合数组和对象解构:

var { a: [a, b] } = { a: [1, 2] };

// a = 1
// b = 2

var [{ a }, { b }] = [{ a: 42 }, { b: 64 }];

// a = 42
// b = 64

相当强大,但是也能人头晕。。

下面看看解构在实战中的用处:

在import语句中使用解构:

import React, { PropTypes } from 'react';

从导入的React中释放PropTypes

如果你要实现一个复杂对象的export时非常有用:

// tomatoSoup.js

const prepareIngredients = /* … */;
const boil = /* … */;
const serve = /* … */;

export default {
prepareIngredients,
boil,
serve
};

// chef.js
import { prepareIngredients, boil } from './tomatoSoup';
// prepare the soup // waiter.js
import { serve } from './tomatoSoup';
// serve the soup!

其次,可以在参数列表中解构,这对于定义无态React组件是有用的:

const runnerInfo = {
streak: 1,
lastRace: {
lapsCompleted: 10,
lapsInRace: 15
}
};

const StreakNotifier = ({ streak }) => {
return <p>Keep it up! {streak} days so far!</p>;
};

const LastRaceDetails = ({ lastRace: { lapsCompleted, lapsInRace }}) => {
return <p>Last time you completed {lapsCompleted} laps out of {lapsInRace}.</p>;
};

还有,能够同时和箭头和传统函数语法一起使用:

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

translateBy2DVector({ x, y }) {
this.x += x;
this.y += y;
}
}

function evaluateRunningPlan({ distance, healthStatus, trainingSchedule, goals }) {
// …
}

第三个用处是能够解构React组件的props& state变量。

class Waiter extends React.Component {
render() {
const { name } = this.props;
const { tip } = this.state;

return (<div>
<p>Hello, my name is {name}! I'll be your waiter today. (Tipped ${tip} so far)</p>
<button onClick={this.tipWaiter}>Tip waiter</button>
</div>);
}
}

上面是使用this.props和this.statr分别命名不同变量,然后使用解构从state和props对象获得变量,如果你要改变状态,你只要从this.state解构出变量放入this.props,再也不需要任何变化。

第四个用处是Redux,因为Redux有一个状态树,是一个大对象,解构能用在这里使得代码清晰,动作ction也是对象,action创建者经常将对象作为参数,引入解构更精确。

const { todos, newTodoText } = store.getState();

const addTodo = ({ id, text }) => ({ type: 'ADD_TODO', id, text });

const todosReducer = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
const { id, text } = action;
return [...state, { id, text }];
default:
return state;
}
}

Javascript专题