はじめに
最近のJavaScriptで追加された
- 分割代入構文(Destructuring assignment syntax)
- スプレッド構文(Spread syntax)
- レスト構文(Rest parameter syntax)
- 略記法
などがしっかりと区別がついていませんでした。
調べながらだと時間がとられるので一度簡単にまとめて覚えなおしてみたいと思いました。
最近の追加等を完全に追えていないので色々抜けがあるかもしれません。
Iterable object
大分ざっくりと言うならば、配列です。
その他、配列のように操作できfor of
文などでループさせられるオブジェクトです。
これには深入りしません。
レスト構文(Rest parameter syntax)
レスト構文は、可変長の仮引数に1つの配列でアクセス出来るようにする構文です。
仮引数の最後に記述します。
function f(a, b, ...args) {
console.log(a, b, args);
}
f(1, 2); // 1, 2, []
f(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
使い道が分かりませんが、rest parameterを使うと個々の仮引数に分割代入出来るようになります。
function f1(a, b, ...[c, d, e]) {
console.log(a, b, c, d, e);
}
f1(1, 2, 3, 4, 5, 6, 7) // 1, 2, 3, 4, 5
スプレッド構文(Spread syntax)
スプレッド構文は、Iterable objectを0個以上の値に展開する構文です。
関数呼び出しでのスプレッド
関数呼び出しで使うと、展開された値を関数に渡します。
function f2(a, b, c) {
console.log(a, b, c);
}
let array = [1, 2, 3, 4, 5, 6];
f2(...array); // 1, 2, 3
Rest parameterとの組み合わせることも出来ます。
Rest parameterとは動作も違うので何回使っても良いです。
function f3(a, b, c, ...args) {
console.log(a, b, c, ...args);
}
let array1 = [1, 2, 3, 4];
let array2 = [5, 6, 7, 8];
let array3 = [9, 10, 11, 12];
f3(...array1); // 1, 2, 3, [4]
f3(...array1, ...array2, ...array3); // 1, 2, 3, [4, 5, 6, 7, 8, 9, 10, 11, 12]
配列リテラルでのスプレッド
配列リテラルで使うと、展開された値が配列の要素として格納されます。
let array1 = [1, 2, 3];
let array2 = array1;
let array3 = [...array1];
console.log(array1, array2, array3); // [1, 2, 3] [1, 2, 3] [1, 2, 3]
array1.push("a");
array2.push("b");
array3.push("c");
console.log(array1, array2, array3);// [1, 2, 3, "a", "b"] [1, 2, 3, "a", "b"] [1, 2, 3, "c"]
オブジェクトリテラルでのスプレッド
オブジェクトリテラルで使うと、名前と値の組に展開されてオブジェクトの要素として格納されます。
let array1 = [1, 2, 3];
let array2 = [3, 4, 5];
let obj1 = {a:1, b:2, c:3};
let obj2 = {c:4, d:5, e:6};
let result1 = {...array1};
let result2 = {...array1, ...array2};
let result3 = {...obj1};
let result4 = {...obj1, ...obj2};
console.log(result1); // {0: 1, 1: 2, 2: 3}
console.log(result2); // {0: 3, 1: 4, 2: 5}
console.log(result3); // {a: 1, b: 2, c: 3}
console.log(result4); // {a: 1, b: 2, c: 4, d: 5, e:6}
分割代入構文(Destructuring assignment syntax)
分割代入構文は、配列またはオブジェクトから値を取り出して別個の変数に代入する構文です。
配列からの分割代入
let [a, b] = [1, 2];
let c, d;
[c, d] = [3, 4]
console.log(a, b, c, d) // 1 2 3 4
必要な値だけを取ることも出来ます。
let [a, b, , , e, f] = [1, 2, 3, 4, 5, 6];
console.log(a, b, e, f) // 1 2 5 6
レスト構文と組み合わせることも可能です。
let [a, b, ...rest] = [1, 2, 3, 4];
console.log(a, b, rest) // 1 2 [3, 4]
デフォルト値も設定できます。
値が代入されなかった場合に使用されます。
let [a=-1, b=-1, c=-1, d=-1] = [1, 2];
console.log(a, b, c, d) // 1 2 -1 -1
オブジェクトからの分割代入
代入するオブジェクトのキー名と変数名を一致させないと代入されません。
また、定義済みの変数に分割代入する場合は、以下のように()
で囲む必要があります。
let { a, b } = { a: 1, b: 2 };
let c, d;
({ c, d } = { c: 3, d: 4 })
let { e, f, ...rest } = { e: 5, f: 6, g: 7, h: 8 };
let { g=-1, h=-1, i=-1, j=-1 } = { g: 7, h: 8 };
console.log(a, b) // 1 2
console.log(c, d) // 3 4
console.log(e, f, rest) // 5 6 {g: 7, h: 8}
console.log(g, h, i, j) // 7 8 -1 -1
オブジェクトのキー名と異なる名前の変数に代入したい場合は、以下のようになります。
慣れるまで戸惑いそうな記述と思いますがどうでしょうか。
let { a: foo, b: bar } = { a: 1, b: 2 } // fooを作成しaの値1を代入。barを作成しbの値2を代入
console.log(foo, bar); // 1 2
Shorthand
関連する構文として、オブジェクト作成時の略記法もあります。
コードに余計なアンダースコアを入れています。
Qiita
のバグのようで、何らかの文字を付けないとリンクとして処理されてしまい表示できないです。
let x = "xx";
let y = "yy";
let z = "zz";
let obj = { x, y, z };
console.log(obj); //{x: "xx" y: "yy" z: "zz"};
let abc = "efg";
let obj = {
_ [abc]: 4,
_ [abc + "hij"]: 5
};
console.log(obj); // {efg: 4, efghij: 5}
応用
関数の戻り値
当たり前の話ですが、分割代入およびスプレッドは、関数の戻り値が配列およびオブジェクトの場合でも出来ます。
関数の戻り値 配列の場合
function f5() {
return [1, 2];
}
let [a, b] = f5();
console.log(a, b); // 1 2
let c = [...f5()];
let d = f5();
console.log(c, d); // [1, 2] [1, 2]
関数の戻り値 オブジェクトの場合
function f6() {
return { a:1, b:2 };
}
let {a, b} = f6();
console.log(a, b); // 1 2
let c = {...f6()};
let d = f6();
console.log(c, d); // { a:1, b:2 } { a:1, b:2 }
関数の引数
分割代入は関数の引数にも使用できます。
関数の引数 配列の場合
f1の方が、融通が利きそうですがどうでしょうか。
function f1([a, b, c]) { // 分割代入
console.log(`${a} ${b} ${c}`);
}
function f2(a, b, c) {
console.log(`${a} ${b} ${c}`);
}
let arr = [1, 2, 3];
f1(arr); // 1 2 3
//f1([...arr]); // 1 2 3 スプレッドする必要はない
f2(...arr); // 1 2 3
let [a, b, c] = [1 ,2, 3];
f1([a, b, c]); // 1 2 3
f2(a, b, c); // 1 2 3
関数の引数 オブジェクトの場合
デフォルト値を個別に設定できるf2
が一番良さそうです。
function f1(obj) { // 代入
console.log(`${obj.a} ${obj.b} ${obj.c}`);
}
function f2({a, b, c}) { // 分割代入
console.log(`${a} ${b} ${c}`);
}
function f3(a, b, c) {
console.log(`${a} ${b} ${c}`);
}
let obj = { a: 1, b: 2, c: 3 };
f1(obj); // 1 2 3
f2(obj); // 1 2 3
// f2({...obj}); // 1 2 3 スプレッドする必要はない
f3(...Object.values(obj)); // 1 2 3
let [a, b, c] = [1 ,2, 3];
f1({a, b, c}); // 1 2 3
f2({a, b, c}); // 1 2 3
f3(a, b, c); // 1 2 3
let obj2 = {x:1, y:2, z:3};
f1({a: obj2.x, b: obj2.y, c: obj2.z}); // 1 2 3
f2({a: obj2.x, b: obj2.y, c: obj2.z}); // 1 2 3
f3(...Object.values(obj2)); // 1 2 3
以下のような記述には、
- デフォルト引数を個別に設定できる
- 引数をオブジェクトで渡す
- 引数を別々の変数で渡す
などの利点があります。
(空のオブジェクトを使って分割代入している)
function f({a=1, b=2, c=3} = {}) { // 便利なデフォルト引数
console.log(`${a} ${b} ${c}`);
}
let obj = { a: "a", b: "b", c: "c" };
f(); // 1 2 3
f({}); // 1 2 3
f(obj); // a b c
f({a: 5, b: 6, c: 7}); // 5 6 7
let [a, b, c] = [8, 9, 0]
f(a, b); // 8 9 0
最後に
分割代入とスプレッドはまとめて覚えたほうが良さそうですね。
関数とのやり取りに使って真価を発揮する感じでしょうか。