javascriptのスプレッド構文あれこれ
3歩あるくと全て忘れる、トリ頭な自分用備忘録です。
スプレッド構文(…)を見るたび、新鮮な「何やコレ」が飛び出すので、まとめておく。
そもそも(…)で何が起きてるんやって話
Mdn公式で確認してみるも、???である。
埒が明かないので、実際に書いてみる。
配列をスプレッド構文で書くと、配列が「開かれた」状態になるのが分かる。へ〜。
let arry = ["わん","にゃー","ぴぃ"];
console.log(...arry); //わん にゃー ぴぃ
じゃあオブジェクトでも使えるんかな??と思ったらエラーが出る。
let obj = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
console.log(...obj);
// Uncaught TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
なぜなら、スプレッド構文は「配列式や文字列などの反復可能オブジェクト」にだけ有効だから。この「反復可能なオブジェクト」が何やねんって話ですが、説明文を読んでもオラにはよく分かりませなんだ。
ざっくり、for…of命令が使えるや〜つとでも覚えておこう。
文字数カウントに使おう!
文字列は「反復可能なオブジェクト」。この特徴を活かして、文字数のカウントに利用しましょう。
String.lengthでは、正確な文字数がカウントできない。(サロゲートペア文字問題)スプレッド構文を使えば、正しく1文字=1カウントとできるので激アツなのだ。
//文字列をスプレッド構文で展開
//文字列が1文字ずつ配列に変換される
let word = "こんにちわ";
console.log([...word]); // ['こ', 'ん', 'に', 'ち', 'わ']
//---------- ▼サロゲートペア文字をカウントする場合▼ ----------
//文字列のlengthをカウント
//1文字なのに2カウントされてしまう!
let word1 = "𩸽";
console.log(word1.length); // 2
//スプレッド構文で一度配列にしてから、配列のlengthをカウント
//正確な文字数がカウントできる!
let word2 = "𩸽";
console.log([...word2].length); // 1
配列の複製 / 追加 / 結合
配列を複製しよう!
再代入は配列そのものを複製しているわけではない。参照(配列の位置情報)だけをコピーしているようなもの。
//再代入
let arry1 = ["わん", "にゃん", "ぴぃ"];
let arry2 = arry1;
console.log(arry2); // ['わん', 'にゃん', 'ぴぃ']
//再代入した配列を変更すると、元の配列も変更されてしまう
let arry1 = ["わん", "にゃん", "ぴぃ"];
let arry2 = arry1;
arry2[0] = "がお〜";
console.log(arry1); // ['がお〜', 'にゃん', 'ぴぃ']
console.log(arry2); // ['がお〜', 'にゃん', 'ぴぃ']
スプレッド構文で複製すると、配列そのものが複製されるのが分かる。(元の配列に影響を与えない)
なお、Array.from()メソッドでも同様のコピーができる。
let arry1 = ["わん", "にゃん", "ぴぃ"];
let arry2 = [...arry1];
arry2[0] = "がお〜";
console.log(arry1); // ['わん', 'にゃん', 'ぴぃ']
console.log(arry2); // ['がお〜', 'にゃん', 'ぴぃ']
ただし、スプレッド構文での複製はシャローコピー(通称:浅いコピー)であることは覚えておきたい。
シャローコピーってなんぞやという話だが…
let arry1 = [["ぱおん", "ちゅ〜"], "わん", "にゃん", "ぴぃ"];
let arry2 = [...arry1];
arry2[0][1] = "こん";
arry2[1] = "ぶー";
console.log(arry1); // [['ぱおん', 'こん'],'わん', 'にゃん', 'ぴぃ']
console.log(arry2); // [['ぱおん', 'こん'],'ぶー', 'にゃん', 'ぴぃ']
入れ子の配列にも、複製した配列の変更が反映してしまうのだ!なんてこったい。
階層が深い配列もちゃんと複製したいよ!ディープコピーしたいよ!って場合は、structuredClone() メソッドとかいうものがあるらしい。
配列に要素を追加しよう!
配列の先頭に追加する時はunshift()、末尾に追加する時はpush()。
スプレッド構文でも同様の効果がございます。
let arry1 = ["わん", "にゃん", "ぴぃ"];
let arry2 = [...arry1, "がお〜"];
console.log(arry2); //['わん', 'にゃん', 'ぴぃ', 'がお〜']
配列同士を結合しよう!
スプレッド構文でもconcat()と同様の効果がございます。
push.apply()なんてのもあるらしいが…apply()自体が分かりづらい。要勉強だな、こりゃ。
let arry1 = ["わん", "にゃん", "ぴぃ"];
let arry2 = ["ぱおん", "ちゅ〜"];
let arry3 = [...arry1, ...arry2];
console.log(arry3); // ['わん', 'にゃん', 'ぴぃ', 'ぱおん', 'ちゅ〜']
配列→オブジェクトに変換してみよう!
配列をオブジェクト内で展開すると、プロパティ名=インデックス番号、値=配列の要素になる。
let arry = ["わん", "にゃん", "ぴぃ"];
let obj = { ...arry };
console.log(obj); // {0: 'わん', 1: 'にゃん', 2: 'ぴぃ'}
条件演算子(?:)を使った要素の追加方法
何かに使えそう、でもいざって時には忘れてるんだよな、人生ってそんなものの連続だよねと感じさせてくれる小技。
trueかfalseかで、要素を追加するかしないか分岐させることができる。
falseの時、空の配列が展開されるが、結果には何も追加されていないことに注目。
const isMouse = false;
const arry = ["わん", "にゃん", "ぴぃ", ...(isMouse ? ["ちゅ〜"] : [])];
console.log(arry); // ['わん', 'にゃん', 'ぴぃ']
オブジェクトの複製 / 追加 / 結合
さっき「反復可能なオブジェクト」しかダメって言ったじゃん。
でも、オブジェクトをオブジェクトリテラルで展開するスプレッド構文は通用するんだよね。ここらへんを矛盾と感じてしまうのが、いかにも文系脳って感じだよな〜。俺!俺!俺俺俺!
いちいち立ち止まるな。分からないものを深く考えても意味がないのだ。
「どこで展開されるのか」が一番大事ってことやね…。
オブジェクトを複製しよう!
オブジェクトリテラル{ }の中でスプレッド構文を使うだけで簡単に複製できる。
//再代入だと元のオブジェクトにも影響する
let obj1 = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
let obj2 = obj1;
obj2.type = "猫";
console.log(obj1);
// {type: '猫', roar: 'わん', feed: 'ドッグフード'}
console.log(obj2);
// {type: '猫', roar: 'わん', feed: 'ドッグフード'}
//スプレッド構文なら、元のオブジェクトに影響しない
let obj1 = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
let obj3 = { ...obj1 };
obj3.type = "インコ";
console.log(obj1);
// {type: '犬', roar: 'わん', feed: 'ドッグフード'}
console.log(obj3);
// {type: 'インコ', roar: 'わん', feed: 'ドッグフード'}
オブジェクトにプロパティを追加しよう!
Object.プロパティ名 = 値 で追加する方法がメジャーだと思うが、スプレッド構文でも追加できる。
let obj1 = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
let obj2 = { ...obj1, nickname: "さぶちゃん" };
console.log(obj2);
// {type: '犬', roar: 'わん', feed: 'ドッグフード', nickname: 'さぶちゃん'}
オブジェクト同士を結合しよう!
複数のプロパティを追加したい時に役立ちそう。
assign()でもできるらしい。使ったことないけど。
let obj1 = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
let obj2 = {
eng: "dog",
age: 10,
breed: "ブルドッグ",
};
let obj3 = { ...obj1, ...obj2 };
console.log(obj3);
// {type: '犬', roar: 'わん', feed: 'ドッグフード', eng: 'dog', age: 10, breed: 'ブルドッグ'}
条件演算子(?:)を使ったプロパティの追加方法
trueかfalseかで、プロパティを追加するかしないか分岐させることができる。falseの場合、空のプロパティを展開しますが、結果には何も追加されていないことに注目。
const isChihuahua = false;
const obj = {
eng: "dog",
age: 10,
...(isChihuahua ? { breed: "ブルドッグ" } : {}),
};
console.log(obj);
// {eng: 'dog', age: 10}
関数の引数に出現するスプレッド構文
なんと、関数の引数にも渡せるのでございます。
こんなん初見でやられたら心が折れるて。堪忍しちくり。けど、これを使う場面がいまいち想像つかないな…??
let arry = ["わん", "にゃん", "ぴぃ"];
function spFunc(value1, value2) {
console.log(value1);
console.log(value2);
}
spFunc(...arry);
// わん
// にゃん
例では配列に3つの要素がありますが、関数が第二引数までしか受け取らないので、3つ目は無視されちゃいます。かわいそう。
分割代入でも活躍するスプレッド構文
配列の分割代入
スプレッド構文を使うことで、まとめて変数に代入できちゃう。
let arry1 = ["わん", "にゃん", "ぴぃ", "ぱおん", "ちゅ〜"];
let [dog, cat, ...other] = arry1;
console.log(dog); // わん
console.log(cat); // にゃん
console.log(other); // ['ぴぃ', 'ぱおん', 'ちゅ〜']
オブジェクトの分割代入
配列と同じく。
let obj = {
type: "犬",
roar: "わん",
feed: "ドッグフード",
};
let { type, ...other } = obj;
console.log(type); // 犬
console.log(other); // {roar: 'わん', feed: 'ドッグフード'}
まとめられたのはこれぐらい。
基本の使い方しか押さえてないので、実際の使いどころについてはまだ全然掴みきれてない感じ…。
間違っているところがあればつっこんでくださいm(_ _)m