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