ES2015で拡張されたObjectの記法と分割代入を活用する

# ES2015で拡張されたObjectの記法と分割代入を活用する

実際に使って勉強しながら、ES5とES2015でどのように書き方が変わるか比較してみました。

今回勉強するにあたって、
[lukehoban](https://github.com/lukehoban)氏の[es6features](https://github.com/lukehoban/es6features)を参考にしています。
以下に非常に簡潔にまとまったサンプルがあるので、興味のある方はこちらもご覧ください。
https://github.com/lukehoban/es6features#enhanced-object-literals
https://github.com/lukehoban/es6features#destructuring

本文中のコードの動作確認はNode.js 7.2.0で行っています。

## 拡張されたObjectの記法について

ES2015では、Objectの初期化時にこれらの事が出来るようになりました。

* メソッド宣言の簡略化
* プロパティ名と変数名が一致する時の簡略化
* プロパティ名の動的な定義

etc...

### メソッド宣言の簡略化

ES5でメソッドを表現したい時は、値に`function(){}`をセットする必要がありましたが、
プロパティ名に`()`を付けることでこれを省略できるようになりました。
他の言語におけるメソッドと似た表現になります。

**ES5**

```js
var rectangle = {
	width: 400,
	height: 300,
	area: function() {
		return this.width * this.height; // => 120000
	}
};
```

**ES2015**

```js
const rectangle = {
	width: 400,
	height: 300,
	area() { 
		return this.width * this.height; // => 120000
	}
};
```

#### `__proto__`をセットすれば親のメソッドも呼べる

**ES2015**

```js
const triangle = {
	__proto__: rectangle,
	area() {
		return super.area() / 2; // => 60000
	}
};
```

### プロパティ定義の簡略化

プロパティ名と、値としてセットする変数の名前が一致する時に、
変数名を省略して書けるようになりました。

**ES5**

```js
var firstName = 'Taro';
var secondName = 'Yamada';

var user = {
	firstName: firstName,
	secondName: secondName
};
// user => { firstName: 'Taro', secondName: 'Yamada' }
```

**ES2015**

```js
const firstName = 'Taro';
const secondName = 'Yamada';

const user = {
	firstName,
	secondName
};
// user => { firstName: 'Taro', secondName: 'Yamada' }
```

### プロパティ名の動的な定義

Objectの初期化時に、動的にプロパティ名をセット出来るようになりました。

**ES5**

```js
var currentId = 1;
var getNextId = function() { return currentId++; };

var users = {};
users['user' + getNextId()] = 'Bob';
users['user' + getNextId()] = 'Tom';
users['user' + getNextId()] = 'Mary';
// users => { user1: 'Bob', user2: 'Tom', user3: 'Mary' }
```

**ES2015**

```js
let currentId = 1;
const getNextId = () => currentId++;

const users = {
	[`user${getNextId()}`]: 'Bob',
	[`user${getNextId()}`]: 'Tom',
	[`user${getNextId()}`]: 'Mary'
};
// users => { user1: 'Bob', user2: 'Tom', user3: 'Mary' }
```

### 利用パターン

#### Enum風Objectと動的プロパティ定義をセットで使う

```js
const Color = {
	RED: 1,
	BLUE: 2,
	GREEN: 3
};

const ColorNames = {
	[Color.RED]: '赤',
	[Color.BLUE]: '青',
	[Color.GREEN]: '緑'
};
// ColorNames => { '1': '赤', '2': '青', '3': '緑' }

const getColorName = (color) => ColorNames[color];

console.log(getColorName(Color.GREEN)); // => 緑
```

## 分割代入

Array, Objectのパターンマッチングによる分割代入が出来るようになりました。

### 分割代入の基本的な使い方

#### 配列からの分割代入

```js
const ppap = ['Pen', 'Pinapple', 'Apple', 'Pen'];
const [p1, p2, a, p3] = ppap;

console.log(p1); // => Pen
console.log(a); // => Apple
```

#### Objectからの分割代入

```js
const pp = {
	fruit: 'Pinapple',
	stationery: 'Pen'
};

const { fruit, stationery } = pp;

console.log(fruit); // => Pinapple
```

#### 関数の引数でのマッチング

```js
const ap = {
	fruit: 'Apple',
	stationery: 'Pen'
};

const showFruit = ({ fruit, stationery }) => console.log(fruit);

showFruit(ap); // => 'Apple'
```

#### マッチしなかった時に使うデフォルト値の設定

```js
const greeting = ({ name, language = 'en' }) => { // languageのデフォルト値を'en'にする
	let msg;
	switch(language) {
		case 'ja':
			msg = 'こんにちは！';
			break;
		case 'en':
			msg = 'Hello!';
			break;
		default:
			msg = 'No language matches';
	}
	console.log(`${name}, ${msg}`);
};

const name = 'Taro';
greeting({ name, language: 'ja' }); // => Taro, こんにちは！
greeting({ name }); // => Taro, Hello！
```

### 利用パターン

#### mapと組み合わせて分割代入する

**ES5**

```js
var names = ['Bob', 'Tom', 'Mary'];
var users = names.map(function(name) { // namesをUserオブジェクトに変換
	return new User({ name: name });
};
var bob = users[0];
var tom = users[1];
var mary = users[2];
```

**ES2015**

```js
const names = ['Bob', 'Tom', 'Mary'];
const [bob, tom, mary] = names.map(name => new User({ name })); // Userオブジェクトへの変換と代入を一行で行う
```

#### 値の入れ替え
@takeharuさんの[もうはじめよう、ES6~ECMAScript6の基本構文まとめ(JavaScript)~](http://qiita.com/takeharu/items/cbbe017bbdd120015ca0#%E5%88%86%E5%89%B2%E4%BB%A3%E5%85%A5%E3%81%AB%E3%82%88%E3%82%8B%E5%80%A4%E3%81%AE%E5%85%A5%E3%82%8C%E6%9B%BF%E3%81%88)より

**ES5**

```js
var fruit1 = 'Apple';
var fruit2 = 'Pinapple';

console.log(fruit1, fruit2); // => Apple Pinapple

var tmp = fruit1;
fruit1 = fruit2;
fruit2 = tmp;

console.log(fruit1, fruit2); // => Pinapple Apple
```

**ES2015**

```js
let [fruit1, fruit2] = ['Apple', 'Pinapple'];
console.log(fruit1, fruit2); // => Apple Pinapple

[fruit2, fruit1] = [fruit1, fruit2];
console.log(fruit1, fruit2); // => Pinapple Apple
```

####classのconstructorでマッチングを行う

```js
class Pen {
	constructor({ color, length = 8 } = {}) { // Penのデフォルトの長さを8にする。()内の右辺は、引数が無かった時のデフォルト値
		this.color = color;
		this.length = length;
	}
}

const color = 'Black';

const normalPen = new Pen({ color });
console.log(normalPen.color); // => Black
console.log(normalPen.length); // => 8

const longPen = new Pen({ color, length: 12 });
console.log(longPen.color); // => Black
console.log(longPen.length); // => 12

const noArgumentsPen = new Pen();
console.log(noArgumentsPen.color); // => undefined
console.log(noArgumentsPen.length); // => 8
```

**記事修正**
初め、constructorの引数でのマッチングについては関数側でのnullチェックが出来ないので推奨しないとしていましたが、
`({ pattern } = {})`の形でデフォルト値を設定出来るので問題無いとご指摘いただきました。(@tksugimotoさん、ありがとうございます！)

**追記**
下記の補足コード(Case 5)の通り、明示的に`null`を渡した場合(`new Pen(null)`)はマッチングに失敗するとのことでした。
このケースに対応するには、下記の*callName4*のように従来通りの形でnullチェックをする必要がありそうです。
明示的に`null`が渡されることが無ければ、無視してしまっても構わないと思います。(個人・職場などの裁量次第によります…。)

**補足コード**

```js
const callName1 = ({ name = 'Taro' }) => console.log(name);
const callName2 = ({ name = 'Taro' } = {}) => console.log(name); // こちらを推奨
const callName3 = ({ name } = { name: 'Taro' }) => console.log(name);
const callName4 = (params) => {  // 引数でのマッチング無し
    const { name = 'Taro' } = params || {};
    console.log(name);
};

const name = 'Jiro';

// Case 1. Normal Argument
callName1({ name }); // => Jiro
callName2({ name }); // => Jiro
callName3({ name }); // => Jiro
callName4({ name }); // => Jiro

// Case 2. No Arguments
callName1(); // => TypeError: Cannot match against 'undefined' or 'null'.
callName2(); // => Taro
callName3(); // => Taro
callName4(); // => Taro

// Case 3. Blank Object
callName1({}); // => Taro
callName2({}); // => Taro
callName3({}); // => undefined
callName4({}); // => Taro

// Case 4. Built-in Object
callName1(1); // => Taro
callName2(1); // => Taro
callName3(1); // => undefined
callName4(1); // => Taro

// Case 5. Null
callName1(null); // => TypeError: Cannot match against 'undefined' or 'null'.
callName2(null); // => TypeError: Cannot match against 'undefined' or 'null'.
callName3(null); // => TypeError: Cannot match against 'undefined' or 'null'.
callName4(null); // => Taro
```

## 終わりに
基本的にはどれも便利な機能なので、積極的に使っていきたいです。
ただし、コードの密度を上昇させ可読性を損なう危険性があるので、用法用量を守って工夫しながら使う必要がありそうです。