はじめに
株式会社iimonでエンジニアをしているかとうです🦕
先に結論
undefined
をスプレッド構文で展開しようとすると以下の結果になります
結果 | 理由 | |
---|---|---|
オブジェクト { ...undefined } |
✅ エラーにならない(無視される) | スプレッド構文が undefined や null を無視するため |
配列 [...undefined] |
❌ エラーになる(TypeError ) |
undefined は反復可能ではないため |
この記事でわかること
💡 オブジェクトでのスプレッド構文の性質
💡 配列でのスプレッド構文の性質
きっかけ
Github Copilotに次のようなコードに対して、レビューコメントをもらいました。
コード
const getDisplaySetting = (setting) => { if (!setting) return; return { setting }; }; // 後の処理でgetDisplaySettingメソッドをスプレッド構文を用いて展開
コメント
いままで、上記コードのように早期リターンを使用して、無意識にundefined
をスプレッド構文で展開していました😶💦
実行結果
1️⃣ settingがtrue
の場合
const obj = {name: 'TEST'}; const result = {...obj, ...getDisplaySetting('SETTING')}; console.log(result); // { name: 'TEST', setting: 'SETTING' }
2️⃣ settingがfalse
場合
const obj = {name: 'TEST'}; const result = {...obj, ...getDisplaySetting()}; console.log(result); // { name: 'TEST' }
undefined
をスプレッド構文で展開しても問題なさそう、、??😵💫💫
→ undefined
とスプレッド構文の関係を調べました
スプレッド構文
スプレッド構文とは
配列の要素やオブジェクトのプロパティを展開する構文のこと
配列内で展開可能
反復可能な値を展開することができる
const arr1 = [1, 2]; const arr2 = [...arr1, 3]; // [1, 2, 3]
🔍 詳しく
「反復可能な値(iterable)」とは、[Symbol.iterator]()
メソッドを持つ値のことです。これを持っていることで、反復処理を可能にします。
[Symbol.iterator]()
は、反復される必要があるときに呼び出される関数で、メソッドの返り値であるイテレーターは、反復される値を取得するために使用されます。
[Symbol.iterator]()
メソッドを持つ値は何ができる?
- 配列リテラルでスプレッド構文を用いて展開可能
for...of
を用いたループ処理が可能
反復可能な値 | 例 |
---|---|
配列 | ['a', 'b', 'c'] |
文字列 | 'hello' |
Map |
new Map([['a', 1], ['b', 2]]) |
Set |
new Set([1, 2, 3]) |
NodeList |
document.querySelectorAll('div') |
💡 for...of
ループを使用できる値は、スプレッド構文を用いて配列内で展開可能!
オブジェクト内で展開可能
自身の列挙可能なプロパティを展開することができる
const obj1 = { a: 1, b: 2 }; const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
🔍 詳しく
「列挙可能なプロパティ」とは、for...in
ループや Object.keys()
で反復処理をすることができるプロパティのこと。
「自身のプロパティ」とは、プロトタイプから継承されたものでなく、そのオブジェクト自体が持っているプロパティのこと。
💡 for...in
ループを使用できる自身のプロパティは、スプレッド構文を用いてオブジェクト内で展開可能!
背景
ECMAScriptの仕様
ECMAScriptの仕様で、null
とundefinded
はオブジェクト内でスプレッド展開すると無視されるようになっています。
Null/Undefined Are Ignored
let emptyObject = { ...null, ...undefined }; // no runtime error
https://github.com/tc39/proposal-object-rest-spread/blob/main/Spread.md
プリミティブはオブジェクトに展開可能
mdnでプリミティブはオブジェクトに展開できると記述があり、null
やundefinded
が展開できることがわかります。
すべてのプリミティブはオブジェクトに展開できます。
プリミティブ値とは?
オブジェクト以外の不変な値のこと
string
number
bigint
boolean
symbol
null
undefined
プリミティブ値のスプレッド展開例
オブジェクト内
const obj1 = { ...null }; // {} const obj2 = { ...undefined }; // {} const obj3 = { ...false }; // {} const obj4 = { ...3 }; // {} const obj5 = { ...'abc' }; // { 0: 'a', 1: 'b', 2: 'c' }
配列内
const arr1 = [...null]; // TypeError: null is not iterable const arr2 = [...undefined]; // TypeError: undefined is not iterable const arr3 = [...false]; // TypeError: false is not iterable const arr4 = [...3]; // TypeError: 3 is not iterable const arr5 = [...'abc']; // ['a', 'b', 'c']
undefinedをスプレッド構文で展開する
オブジェクト内
オブジェクト内でundefined
をスプレッド構文で展開したとき
const obj = { ...undefined }; // {}
配列内
配列内でundefined
をスプレッド構文で展開したとき
const arr = [...undefined]; // TypeError: undefined is not iterable
配列内でスプレッド構文を用いて展開したい場合、対象が反復可能である必要がある
→ undefined
は反復可能な値ではないため、配列内で展開することができない
使用例
オブジェクト内でundefined
がスプレッド展開してもエラーが発生しないことを利用して、簡潔に安全な処理を書くことができます。
通常
const mergeUser = (setting) => { const user = { name: "NAME" }; if (setting && setting.user) { console.log({ ...user, ...setting.user }); } else { console.log(user); } }; mergeUser(); // { name: 'NAME' } mergeUser({ user: { age: 25 } }); // { name: 'NAME', age: 25 }
スプレッド構文とオプショナルチェーンを使用
const createUser = (setting) => { const user = { name: "NAME", ...setting?.user, } console.log(user); } createUser(); // { name: "NAME" } createUser({ user: { age: 25 } }); // { name: "NAME", age: 30 }
setting
または setting.user
が undefined
になる場合でも、スプレッド構文がundefined
を無視するため、存在確認のための条件チェックなしにオブジェクトを結合することができます🌟
まとめ
オブジェクト内での
undefined
とnull
のスプレッド展開→ 無視される仕様のためエラーにならない
配列内での
undefined
とnull
のスプレッド展開→ 反復可能ではないので展開できずにエラーになる
最後に
ここまで読んでいただきありがとうございます!🌷
弊社ではエンジニアを募集しています。
この記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談でお話ししましょう✨
iimon採用サイト / Wantedly / Green