同じ ... が、広げる側と集める側で逆の意味になります。配列・オブジェクト・関数それぞれの使い方、後ろ勝ちの上書き、複製が1段しか効かない点までを実行して確かめます。
... には2つの意味があります。置く場所で決まります。
example.js
console.log([...[1, 2], 3]);
const [first, ...rest] = [1, 2, 3];
console.log(first, rest);
出力書き換えて実行できます
[ 1, 2, 3 ]
1 [ 2, 3 ]
example.js
const nums = [1, 2];
console.log([...nums, 3]);
console.log([0, ...nums]);
console.log([...nums, ...nums]);
console.log(Math.max(...[1, 5, 3]));
出力書き換えて実行できます
[ 1, 2, 3 ]
[ 0, 1, 2 ]
[ 1, 2, 1, 2 ]
5
- 右辺(値を作る側)… 広げる(スプレッド)
- 左辺(受け取る側)… 集める(残余)
同じ記号で、逆の働きです。
配列を広げる
example.js
console.log([...'あい']);
console.log([...new Set([1, 1, 2])]);
console.log([...new Map([['a', 1]])]);
出力書き換えて実行できます
[ 'あ', 'い' ]
[ 1, 2 ]
[ [ 'a', 1 ] ]
example.js
console.log({ ...{ a: 1 }, b: 2 });
console.log({ ...{ a: 1 }, ...{ a: 2 } });
console.log({ a: 1, ...{ a: 2 } });
console.log({ ...{ a: 2 }, a: 1 });
出力書き換えて実行できます
{ a: 1, b: 2 }
{ a: 2 }
{ a: 2 }
{ a: 1 }
関数の引数としても広げられます。 Math.max() は配列を受け取らないので、
... で1つずつに開いてから渡します。
要素が非常に多いと、引数の上限に当たることがあります。件数が読めないときは reduce() を使ってください。
文字列も広げられる
example.js
function withDefaults(options) {
return { retry: 1, timeout: 1000, ...options };
}
console.log(withDefaults({ timeout: 50 }));
console.log(withDefaults({}));
出力書き換えて実行できます
{ retry: 1, timeout: 50 }
{ retry: 1, timeout: 1000 }
example.js
console.log({ ...null, ...undefined, a: 1 });
出力書き換えて実行できます
{ a: 1 }
広げられるのは反復可能なものです。配列・文字列・Set・Map が該当します。
文字列は符号位置ごとに分かれるので、絵文字が割れません。
→ 文字を正しく数える
オブジェクトを広げる
example.js
console.log({ ...[1, 2] });
出力書き換えて実行できます
{ '0': 1, '1': 2 }
example.js
const [first, ...rest] = [1, 2, 3];
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(first, rest);
console.log(a, others);
出力書き換えて実行できます
1 [ 2, 3 ]
1 { b: 2, c: 3 }
同じ鍵があれば後ろが勝ちます。
既定値を先に、上書きを後ろに置く、という順で書きます。
example.js
function sum(...nums) {
return nums.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3));
console.log(sum());
出力書き換えて実行できます
6
0
javascript
const original = { x: { y: 1 } };
const copied = { ...original };
copied.x.y = 99;
console.log(original.x.y);
99
null や undefined を広げても落ちません。
example.js
const original = { x: { y: 1 } };
const copied = structuredClone(original);
copied.x.y = 99;
console.log(original.x.y, copied.x.y);
出力書き換えて実行できます
1 99
javascript
function wrong(...rest, last) {}
Object.keys(null) は落ちるのに、こちらは落ちません。
この差が、原因の切り分けを難しくします。
→ TypeError: Cannot convert undefined or null to object
配列をオブジェクトに広げると
example.js
let items = [];
for (const n of [1, 2, 3]) {
items = [...items, n];
}
console.log(items);
出力書き換えて実行できます
[ 1, 2, 3 ]
添字が鍵になります。 意図してやることは、まずありません。
配列を渡すつもりでオブジェクトに広げていないか確かめてください。
集める側
分割代入で
残りをまとめて受け取れます。
鍵を1つ除いたオブジェクトを作るのに、いちばん短い書き方です。
→ オブジェクトから必要な鍵だけ取り出す・除く
関数の引数で
渡されなければ空配列です。
→ 関数の引数 — 既定値・可変長・分割代入
いちばんの落とし穴:1段しか複製されない
copied は新しいオブジェクトですが、x は同じものを指しています。
これを浅いコピーと呼びます。
深く複製するなら structuredClone() です。
→ オブジェクトを結合する
よくある間違い
関数の途中に残余を書く
残余引数は最後にしか置けません。
これは構文として読めません。実行する前に弾かれます。
構文の誤りなので、このページでは実行例にできません。
push の代わりに使って遅くする
結果は正しいのですが、毎回すべてを作り直しています。
件数が増えると急に遅くなります。
繰り返しの中で増やすなら push() を使ってください。
→ push() / pop() / shift() / unshift()
まとめ
- 同じ
... が、右辺では広げ、左辺では集める
- 広げられるのは反復可能なもの(配列・文字列・
Set・Map)
- オブジェクトは後ろが勝つ。既定値を先に書く
null / undefined を広げても落ちない
- 配列をオブジェクトに広げると添字が鍵になる
- 複製は1段だけ。 深く複製するなら
structuredClone()
- 繰り返しの中で
[...items, n] を使わない