分割代入 (Destructuring assignment)
《 初回公開:2018/09/23 , 最終更新:2023/04/12 》
【 目次 】
node.jsのModulesを調べていてJavaScriptの見慣れない構文に出くわした。
const { PI } = Math;
これは
const PI = Math.PI;
を意味しているらしい。
ES2015(ES6)の構文で分割代入 (Destructuring assignment)と呼ばれているようだ。
Destructuring assignmentとは破壊的な(分解して)割り当てるとでも訳したらいいのかな。
javascript 分割代入
分割代入を利用すると複数の変数に同時に配列やオブジェクトのプロパティの複数の値を代入する事ができる。
分割代入には以下のように、様々なバリエーションがある。
配列から分割代入
var [a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
このような単純に複数の変数に対して値を代入する事は、従来の構文でも可能だが。
var a = 1, b = 2, c = 3;
// a= 1, b= 2 c= 3
b = a; c = a;
// a= 1, b= 1 c= 1
var a = 4, x, c = 6;
// a= 4, x= undefined c= 6
でも、分割代入ではもっと複雑な代入のパターンがいろいろと。
オブジェクトからの分割代入
オブジェクトリテラルからの分割代入
let { name, age } = { name: "愚鈍人", age: 99 };
console.log(`${name}は${age}才です。`); // 愚鈍人は99才です。
クラスのインスタンスからの分割代入
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person = new Person("愚鈍人", 99);
let { name, age } = person;
console.log(`${name}は${age}才です。`); // 愚鈍人は99才です。
変数の宣言と分割代入
これまでの例では変数宣言にvar
やlet
を使ってきたが、当然、constでも。
const [a, b, c] = [1, 2, 3];
宣言後の割り当て。
これも当たり前だが、const以外は変数の宣言後に分割代入をおこなう事ができる
let a, b, c;
[a, b, c] = [1, 2, 3];
オブジェクトからの分割代入では、宣言後の割り当てに注意が必要で、 カッコで囲む必要がある。
let a, b, c;
({ a, b, c } = { a: 1, b: 2, c: 3 });
{ a, b, c }
はブロックと解釈されてしまうため上記のコードは2行目の()が無いとエラーになる。
変数の宣言と代入を同時におこなう以下のコードなら()が無くてもエラーは発生しない。
let { a, b, c } = { a: 1, b: 2, c: 3 };
イテラブルなオブジェクトからの代入
配列から分割代入はイテラブルなオブジェクトに対しても適用できる。
Setオブジェクトはイテラブル。
let [a, b, c] = "abc"; // ["a", "b", "c"]
console.log(a);
console.log(b);
console.log(c);
let [one, two, three] = new Set([1, 2, 3]);
console.log(one);
console.log(two);
console.log(three);
Mapオブジェクトもイテラブル。
でも、Mapの場合はちょっと複雑で配列の入れ子になっていて。
const map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
let [[a, av], [b, bv], [c, cv]] = map;
console.log(a, av); // a 1
console.log(b, bv); // b 2
console.log(c, cv); // c 3
変数のスワップ
配列の分割代入では変数のスワップを簡単におこなう事ができる。
変数のスワップ(3変数の入れ替え)
var a = 1, b = 2, c = 3;
[a, b, c] = [b, c, a]
console.log("a=", a); // a= 2
console.log("b=", b); // b= 3
console.log("c=", c); // c= 1
配列の要素どうしを交換。
const arr = [1,2,3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1,3,2]
関数やメソッドの分割代入
配列の要素が関数に対しての分割代入
let [fa, fb, fc] = [() => 1, () => 2, () => 3]
console.log(fa()); // 1
console.log(fb()); // 2
console.log(fc()); // 3
メソッドの分割代入
class Foo {
fa = () => 1;
fb = () => 2;
fc = () => 3;
}
let { fa, fb, fc } = new Foo();
console.log(fa()); // 1
console.log(fb()); // 2
console.log(fc()); // 3
左辺の変数と代入される右辺の値の数が異なる場合
配列の前の部分のみを受け取る
左辺の変数の数が右辺の値の数より少ない場合は
const [a, b, c] = [1, 2, 3, 4, 5];
// a= 1, b= 2 c= 3
残りの右辺の値は捨てられる。
足りない値はundefinedが代入される
左辺の変数の数が右辺の値の数より多い場合は
const [a, b, c, d, e] = [1, 2, 3];
// a= 1, b= 2 c= 3, d= undefined, e= undefined
足りない値はundefinedが代入される。
配列の要素が歯抜けになっていて一部分のみを取り出す
const [a, , b, , c] = [1, 2, , 4, 5];
// a= 1, b= undefined, c= 5
残りの値を配列として受け取る - レスト構文
最後の変数に残り部分の変数をすべて配列として格納。
const [a, b, ...rest] = [1, 2, 3, 4, 5];
// a= 1, b= 2, rest= [3, 4, 5]
配列の残余部分への変数の代入
...rest
の構文はレスト構文と呼ばれているようだ。
また、分割代入の構文は関数の引数に対しても適用できるが、
レスト構文が関数の引数として用いれれる場合には残余引数とも呼ばれる。
function f(a, b, ...rest) {
console.log(`a = ${a}, b = ${b}, rest = ${rest}`);
}
f(1, 2, 3, 4, 5);
// a = 1, b = 2, rest = 3,4,5
残余引数については別の記事(可変長引数)にて、再度とりあげる事にする。
レスト構文はあくまでも最後の変数のみに使用できる。
以下の構文はすべてSyntaxErrorが発生する
const [...a, b] = [1, 2, 3];
const [a, ...b, c] = [1, 2, 3];
const [a, ...b,] = [1, 2, 3];
レスト構文はオブジェクトに対しても有効で
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
// a= 1, b= 2, rest= {c: 3, d: 4}
既定値の指定
足りない分の値に既定値を指定できる。
取り出した値が undefined だった場合に使用される既定値を指定できる。
const [a = 11, b = 12, c, d, e = 14] = [1, , 3];
// a = 1, b = 12, c = 3, d = undefined, e = 14
オブジェクトの場合も同様で
const { a = 11, b = 12, c, d, e = 14 } = { a: 1, c: 3 }
// a = 1, b = 12, c = 3, d = undefined, e = 14
新しい変数名への代入
オブジェクトからの分割代入の場合、プロパティ名が変数として割り当てられるが
const { a, b, c } = { a: 1, b: 2, c: 3 }
// a = 1, b = 2, c = 3
プロパティ名に束縛されずに自由に変数名を指定する事もできる。
const { a: x, b: y, c: z } = { a: 1, b: 2, c: 3 }
// x = 1, y = 2, z = 3
既定値を指定した上で新しい変数名への代入をおこなうには
const { a: x = 11, b: y = 12, c: z, d: v, e: w = 14 } = { a: 1, c: 3 }
// x = 1, y = 12, z = 3, v = undefined, w = 14
ネストした配列やオブジェクトに対する分割代入
配列やオブジェクトが複雑にネストしていても分割代入が可能で、
配列やオブジェクトの構造に合わせて、変数をマッピングして配置する事で値を代入できる。
const mens =
[
{
name: "愚鈍人",
wife: [
{ name: "愚鈍人の妻", children: ["太郎", "花子"] },
{ name: "愚鈍人のお妾さん", children: ["一郎", "次郎"] },
]
},
{
name: "白鳥麗子"
}
];
const [{ name: menName, wife: [, { name: WifeName, children: [firstchild, secondChild] }] }] = mens;
console.log(`menName: ${menName}, WifeName: ${WifeName}, firstchild: ${firstchild}, secondChild: ${secondChild}`);
// menName: 愚鈍人, WifeName: 愚鈍人のお妾さん, firstchild: 一郎, secondChild: 次郎
分割代入の関数への適用
分割代入の関数の引数への適用
残余引数の例のように分割代入は関数の引数にも適用できる。
前項の「ネストした配列やオブジェクトに対する分割代入」のサンプルコードのような複雑な分割代入であっても例外ではない。
function f([{ name: menName, wife: [, { name: WifeName, children: [firstchild, secondChild] }] }]){
console.log(`menName: ${menName}, WifeName: ${WifeName}, firstchild: ${firstchild}, secondChild: ${secondChild}`);
}
f(mens);
// menName: 愚鈍人, WifeName: 愚鈍人のお妾さん, firstchild: 一郎, secondChild: 次郎
関数の引数に対する既定値の設定
1 2 3 4 5 6 7 8 9 10 11 |
|
このコードの1行目にある = {}
の意味がわかりずらいが、= {}
を削除すると、
10行目のdrawChart関数の呼び出しでエラーが発生する。
何故なら引数を省略した場合の既定値が指定されていない為であり、
= {}
を指定する事により空のオブジェクトが既定値となり、その後にsizeやcoords, radiusの既定値が与えられる事になる。
関数から返される複数の値を分割代入
シンプルな例。
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
複雑な例
function f() {
return [
{
name: "愚鈍人",
wife: [
{ name: "愚鈍人の妻", children: ["太郎", "花子"] },
{ name: "愚鈍人のお妾さん", children: ["一郎", "次郎"] },
]
},
{
name: "白鳥麗子"
}
];
}
const [{ name: menName, wife: [, { name: WifeName, children: [firstchild, secondChild] }] }] = f();
console.log(`menName: ${menName}, WifeName: ${WifeName}, firstchild: ${firstchild}, secondChild: ${secondChild}`);
// menName: 愚鈍人, WifeName: 愚鈍人のお妾さん, firstchild: 一郎, secondChild: 次郎
イテレーターでの分割代入の利用
for...of文で取り出したイテレーター(反復可能オブジェクト)が返すオブジェクトの要素に対しても分割代入が適用可能で
const people = [
{
name: 'Mike Smith',
family: {
mother: 'Jane Smith',
father: 'Harry Smith',
sister: 'Samantha Smith'
},
age: 35
},
{
name: 'Tom Jones',
family: {
mother: 'Norah Jones',
father: 'Richard Jones',
brother: 'Howard Jones'
},
age: 25
}
];
for (const { name: n, family: { father: f } } of people) {
console.log('Name: ' + n + ', Father: ' + f);
}
// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"
同様にMapからその内容を取り出すには
const map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
計算プロパティ名を使って分割代入
計算されたオブジェクトのプロパティの名前と分割代入
let key = 'z',i=3;
let { [key + i]: foo } = { z3: 'bar' };
console.log(foo); // "bar"
計算プロパティ名(Computed object property names)については。
プロトタイプチェーンをたどって値を取得
オブジェクトが分割されるときにはプロトタイプチェーンが参照される
let obj = {self: '123'};
obj.__proto__.prot = '456';
const {self, prot} = obj;
// self "123"
// prot "456" (プロトタイプチェーンへのアクセス)
他にも
他にもいろいろ応用できて、
スワップではないが、配列をオブジェクトにマッピング。
let obj = new Object();
[obj.a, obj.b, obj.c] = [1, 2, 3];
console.log(JSON.stringify(obj)); // {"a":1,"b":2,"c":3}
splitメソッドを利用して
let obj = new Object();
[obj.x, obj.y, obj.z] = "a,b,c".split(",");
console.log(JSON.stringify(obj)); // {"x":"a","y":"b","z":"c"}
別モジュールより値を取得
メインモジュール
var [b, a] = require('./sub_module');
console.log("a=", a); // a= 20
console.log("b=", b); // b= 10
サブモジュール sub_module.js
module.exports = [10, 20];
別モジュールより値を取得(連想配列版)
メインモジュール
var { b, a } = require('./sub_module');
console.log("a=", a); // a= 10
console.log("b=", b); // b= 20
サブモジュール sub_module.js
module.exports.a = 10;
module.exports.b = 20;