40
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptAdvent Calendar 2016

Day 5

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

Last updated at Posted at 2016-12-04

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

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

今回勉強するにあたって、
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

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

ES2015

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

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

ES2015

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

プロパティ定義の簡略化

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

ES5

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

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

ES2015

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

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

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

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

ES5

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

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と動的プロパティ定義をセットで使う

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のパターンマッチングによる分割代入が出来るようになりました。

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

配列からの分割代入

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

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

Objectからの分割代入

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

const { fruit, stationery } = pp;

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

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

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

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

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

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

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

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

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

値の入れ替え

@takeharuさんのもうはじめよう、ES6~ECMAScript6の基本構文まとめ(JavaScript)~より

ES5

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

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

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

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

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が渡されることが無ければ、無視してしまっても構わないと思います。(個人・職場などの裁量次第によります…。)

補足コード

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

終わりに

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

40
38
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?