JavaScript ES6+モダン構文まとめ
テンプレートリテラル・オプショナルチェーニング
JavaScriptのES6以降のモダン構文を解説。テンプレートリテラル、オプショナルチェーニング、Null合体演算子、構造化代入の高度な使い方まで学べます。
こんな人向けの記事です
- ES6以降の新しい構文を学びたい
- オプショナルチェーニングやNull合体演算子を使いこなしたい
- モダンJavaScriptを書きたい
Step 1テンプレートリテラル
ES6で導入されたテンプレートリテラルは、バッククォート(`)で囲む新しい文字列記法です。従来の文字列連結に比べ、はるかに読みやすいコードが書けます。
基本:バッククォートと式の埋め込み
テンプレートリテラルでは ${ } の中に任意のJavaScript式を埋め込めます。
const name = '太郎';
const age = 25;
// 従来の文字列連結
const oldStyle = '名前: ' + name + '、年齢: ' + age + '歳';
// テンプレートリテラル
const newStyle = `名前: ${name}、年齢: ${age}歳`;
// 式の埋め込み(計算や関数呼び出しもOK)
const msg = `来年は${age + 1}歳です`;
const upper = `大文字: ${name.toUpperCase()}`;
複数行文字列
テンプレートリテラルは改行をそのまま含められます。HTMLテンプレートの生成に便利です。
// 従来の方法(\n で改行)
const oldHtml = '<div>\n <h1>タイトル</h1>\n <p>本文</p>\n</div>';
// テンプレートリテラル(そのまま改行)
const newHtml = `<div>
<h1>タイトル</h1>
<p>本文</p>
</div>`;
// 配列から動的にリスト生成
const items = ['りんご', 'バナナ', 'みかん'];
const list = `<ul>
${items.map(item => `<li>${item}</li>`).join('\n ')}
</ul>`;
タグ付きテンプレート
テンプレートリテラルの前に関数名を置くと、その関数で文字列を加工できます。XSS対策のエスケープ処理などに使えます。
// タグ関数の定義
function escapeHtml(strings, ...values) {
const escape = (str) => String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
return strings.reduce((result, str, i) => {
return result + str + (i < values.length ? escape(values[i]) : '');
}, '');
}
// タグ付きテンプレートとして使用
const userInput = '<script>alert("XSS")</script>';
const safe = escapeHtml`<p>ユーザー入力: ${userInput}</p>`;
// "<p>ユーザー入力: <script>alert("XSS")</script></p>"
有名ライブラリでも活用されています。
styled-components: styled.div`color: red;`
GraphQL: gql`query { user { name } }`
タグ関数は文字列の各パーツと埋め込み値を別々に受け取るため、安全な処理が可能です。
Step 2オプショナルチェーニング(?.)
オプショナルチェーニング(?.)はES2020で導入された演算子です。ネストしたオブジェクトのプロパティに安全にアクセスでき、途中で null や undefined があれば即座に undefined を返します。
基本的な使い方
const user = {
name: '太郎',
address: {
city: '東京',
zip: '100-0001'
}
};
// 従来の安全なアクセス(冗長)
const city1 = user && user.address && user.address.city;
// オプショナルチェーニング(簡潔)
const city2 = user?.address?.city; // "東京"
// 存在しないプロパティ → undefined(エラーにならない)
const country = user?.address?.country; // undefined
// user が null でも安全
const nullUser = null;
const name = nullUser?.name; // undefined(TypeError にならない)
メソッド呼び出しと配列アクセス
const obj = {
greet() { return 'こんにちは'; },
items: ['a', 'b', 'c']
};
// メソッドの安全な呼び出し
obj.greet?.(); // "こんにちは"
obj.nonExistent?.(); // undefined(エラーにならない)
// 配列要素の安全なアクセス
obj.items?.[0]; // "a"
obj.items?.[10]; // undefined
obj.nothing?.[0]; // undefined
// delete と組み合わせ
delete user?.address?.zip; // 安全に削除
APIレスポンスでの実践例
// APIレスポンスの安全な読み取り
async function getUserCity(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// data.user が存在しない、address が null でも安全
const city = data?.user?.address?.city ?? '不明';
const phone = data?.user?.contacts?.find(c => c.type === 'phone')?.number;
return { city, phone };
}
// DOM操作での安全なアクセス
const value = document.querySelector('#myInput')?.value ?? '';
const text = document.querySelector('.content')?.textContent?.trim();
?. は読み取り専用です。代入の左辺には使えません。
user?.name = '太郎' は SyntaxError になります。
また、短絡評価のため ?. 以降の式は評価されません。副作用のある式を置かないようにしましょう。
Step 3Null合体演算子(??)
Null合体演算子(??)はES2020で導入されました。左辺が null または undefined の場合のみ右辺を返します。|| との違いを理解することが重要です。
??と||の決定的な違い
// || は falsy な値(0, '', false, NaN)でも右辺を返す
0 || 10; // 10(0 は falsy)
'' || 'default'; // "default"(空文字は falsy)
false || true; // true(false は falsy)
// ?? は null/undefined のみ右辺を返す
0 ?? 10; // 0(0 はそのまま)
'' ?? 'default'; // ""(空文字はそのまま)
false ?? true; // false(false はそのまま)
null ?? 10; // 10
undefined ?? 10; // 10
この違いは数値設定で特に重要です。
| 値 | 値 || 'デフォルト' | 値 ?? 'デフォルト' |
|---|---|---|
null | 'デフォルト' | 'デフォルト' |
undefined | 'デフォルト' | 'デフォルト' |
0 | 'デフォルト' | 0 |
'' | 'デフォルト' | '' |
false | 'デフォルト' | false |
NaN | 'デフォルト' | NaN |
実践的な使い分け
// ページネーション — 0ページ目を許可したい
function getPage(params) {
// || だと page=0 のとき 1 になってしまう
const page = params.page || 1; // page=0 → 1(バグ!)
const page2 = params.page ?? 1; // page=0 → 0(正しい)
return page2;
}
// 音量設定 — 0(ミュート)を許可
const volume = userSettings.volume ?? 50; // 未設定なら50、0ならそのまま
// フラグ設定 — false を有効な値として扱う
const darkMode = config.darkMode ?? false;
// オプショナルチェーニングとの組み合わせ
const city = user?.address?.city ?? '未設定';
const count = response?.data?.items?.length ?? 0;
?? を使う場面:0、''、false が有効な値になりうるとき(数値設定、フラグ、フォーム入力)
|| を使う場面:falsy な値をすべてデフォルトに置き換えたいとき(表示用テキストなど)
?? は || や && と直接結合できません。括弧が必要です。
a || b ?? c は SyntaxError
(a || b) ?? c または a || (b ?? c) と書きましょう。
Step 4論理代入演算子(||=, &&=, ??=)
ES2021で導入された論理代入演算子は、条件付きで変数に値を代入する省略記法です。設定のデフォルト値適用やキャッシュパターンで活躍します。
3つの論理代入演算子
// ??= — null/undefined のときだけ代入
let a = null;
a ??= 10; // a は 10
let b = 0;
b ??= 10; // b は 0(0 は null でも undefined でもない)
// ||= — falsy のとき代入
let c = '';
c ||= 'default'; // c は "default"
let d = 0;
d ||= 10; // d は 10
// &&= — truthy のとき代入
let e = 'hello';
e &&= 'world'; // e は "world"
let f = '';
f &&= 'world'; // f は ""(空文字は falsy なので代入されない)
等価な従来の書き方
| 論理代入 | 等価な書き方 | 代入条件 |
|---|---|---|
x ??= y | x = x ?? y | x が null/undefined |
x ||= y | x = x || y | x が falsy |
x &&= y | x = x && y | x が truthy |
実践パターン
// パターン1: デフォルト値の設定
function initConfig(options) {
options.timeout ??= 3000;
options.retries ??= 3;
options.baseURL ??= 'https://api.example.com';
return options;
}
initConfig({ timeout: 5000 });
// { timeout: 5000, retries: 3, baseURL: "https://api.example.com" }
// パターン2: キャッシュの遅延初期化
class DataService {
#cache = null;
getCache() {
// キャッシュが null のときだけ生成
this.#cache ??= this.#buildCache();
return this.#cache;
}
#buildCache() {
console.log('キャッシュ構築中...');
return new Map();
}
}
// パターン3: オブジェクトのプロパティ初期化
const stats = {};
for (const item of items) {
stats[item.category] ??= { count: 0, total: 0 };
stats[item.category].count += 1;
stats[item.category].total += item.price;
}
// パターン4: DOM要素の表示切り替え
element.textContent ||= '(データなし)'; // 空文字ならデフォルト表示
論理代入演算子は短絡評価です。x ??= expensiveComputation() で x が既に値を持っている場合、右辺の関数は実行されません。不要な計算を避けられるため、パフォーマンス面でも有利です。
Step 5構造化代入の高度な使い方
ES6で導入された分割代入(Destructuring)は、基本的な使い方だけでなく、ネスト・デフォルト値・リネームなどを組み合わせることで非常に強力になります。
オブジェクトの高度な分割代入
// ネストした分割代入 + リネーム + デフォルト値
const response = {
data: {
user: { name: '太郎', age: 25 },
meta: { page: 1 }
},
status: 200
};
const {
data: {
user: { name: userName, age = 0 }, // リネーム + デフォルト値
meta: { page, total = 100 } // デフォルト値
},
status
} = response;
console.log(userName); // "太郎"
console.log(age); // 25
console.log(total); // 100(response に total がないのでデフォルト値)
console.log(status); // 200
配列の高度な分割代入
// スキップと残余要素
const [first, , third, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(third); // 3
console.log(rest); // [4, 5]
// 変数のスワップ
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1
// 関数の戻り値から必要な値だけ取得
function getMinMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([3, 1, 4, 1, 5]);
console.log(min, max); // 1, 5
// ネストした配列
const [[a1, a2], [b1, b2]] = [[1, 2], [3, 4]];
console.log(a1, b2); // 1, 4
関数パラメータでの分割代入
// オプション引数のパターン
function createUser({
name,
age = 20,
role = 'user',
address: { city = '東京', zip } = {} // ネスト + デフォルト
} = {}) { // 引数自体のデフォルト
return { name, age, role, city, zip };
}
createUser({ name: '太郎' });
// { name: "太郎", age: 20, role: "user", city: "東京", zip: undefined }
createUser({ name: '花子', address: { city: '大阪', zip: '530-0001' } });
// { name: "花子", age: 20, role: "user", city: "大阪", zip: "530-0001" }
createUser();
// { name: undefined, age: 20, role: "user", city: "東京", zip: undefined }
// 配列パラメータ
function sum([a, b, ...rest]) {
return a + b + rest.reduce((s, n) => s + n, 0);
}
sum([1, 2, 3, 4]); // 10
実践パターン:APIレスポンスの整形
// APIレスポンスから必要なデータだけ抽出
async function fetchUsers() {
const response = await fetch('/api/users');
const {
data: users = [],
meta: { totalCount = 0, currentPage = 1 } = {}
} = await response.json();
return { users, totalCount, currentPage };
}
// イベントオブジェクトから抽出
document.addEventListener('click', ({
target,
clientX: x,
clientY: y,
shiftKey
}) => {
console.log(`クリック: (${x}, ${y}) shift=${shiftKey}`);
console.log(`要素: ${target.tagName}`);
});
分割代入のデフォルト値は、値が undefined のときだけ適用されます。null の場合はデフォルト値が使われません。
const { x = 10 } = { x: null }; → x は null(10にはならない)
const { x = 10 } = { x: undefined }; → x は 10
Step 6for...of と for...in の違い
for...of と for...in は似た構文ですが、用途がまったく異なります。混同するとバグの原因になるため、違いを正確に理解しましょう。
基本的な違い
| 特性 | for...in | for...of |
|---|---|---|
| 導入時期 | ES1(最初から) | ES6 |
| 反復対象 | オブジェクトのキー(列挙可能プロパティ) | イテラブルの値 |
| 配列での動作 | インデックス(文字列)を返す | 要素の値を返す |
| プロトタイプチェーン | 辿る(継承プロパティも列挙) | 辿らない |
| 主な用途 | オブジェクトのプロパティ列挙 | 配列・Map・Set などの反復 |
const arr = ['a', 'b', 'c'];
// for...in — インデックス(キー)が返る
for (const key in arr) {
console.log(key); // "0", "1", "2"(文字列!)
console.log(typeof key); // "string"
}
// for...of — 値が返る
for (const value of arr) {
console.log(value); // "a", "b", "c"
}
for...in の落とし穴
// 落とし穴1: プロトタイプチェーンのプロパティも列挙される
Array.prototype.customMethod = function() {};
const arr = ['a', 'b'];
for (const key in arr) {
console.log(key); // "0", "1", "customMethod" ← 意図しないプロパティ!
}
// 対策: hasOwnProperty でフィルタリング
for (const key in arr) {
if (arr.hasOwnProperty(key)) {
console.log(key); // "0", "1"
}
}
// 落とし穴2: 順序が保証されない(整数キー以外)
const obj = { b: 2, a: 1, 10: 'ten', 2: 'two' };
for (const key in obj) {
console.log(key); // "2", "10", "b", "a"(整数キーが先、追加順ではない)
}
for...of の活用
// 文字列の反復(サロゲートペアも正しく処理)
for (const char of '𩸽を食べる') {
console.log(char); // "𩸽", "を", "食", "べ", "る"
}
// Map の反復
const map = new Map([['name', '太郎'], ['age', 25]]);
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Set の反復
const set = new Set([1, 2, 3, 2, 1]);
for (const value of set) {
console.log(value); // 1, 2, 3(重複なし)
}
// entries() でインデックスも取得
const fruits = ['りんご', 'バナナ', 'みかん'];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
// NodeList の反復(DOM)
for (const el of document.querySelectorAll('.item')) {
el.classList.add('active');
}
オブジェクトを for...of で反復する方法
const config = { host: 'localhost', port: 3000, debug: true };
// Object.entries() を使う
for (const [key, value] of Object.entries(config)) {
console.log(`${key} = ${value}`);
}
// Object.keys() — キーだけ
for (const key of Object.keys(config)) {
console.log(key);
}
// Object.values() — 値だけ
for (const value of Object.values(config)) {
console.log(value);
}
配列の反復には for...of を使いましょう。for...in は配列に使うと予期しないプロパティが列挙されるリスクがあります。
for...in → オブジェクトのプロパティ列挙専用
for...of → 配列・Map・Set・文字列などイテラブルの反復用
まとめES6+モダン構文チェックリスト
- テンプレートリテラル — バッククォートで文字列を書き、
${}で式を埋め込む - タグ付きテンプレート — 関数で文字列を安全に加工する
- オプショナルチェーニング
?.— ネストしたプロパティへの安全なアクセス - Null合体演算子
??— null/undefined のみデフォルト値を返す(||との違いを理解) - 論理代入演算子
??=||=&&=— 条件付きの省略代入 - 構造化代入 — ネスト・リネーム・デフォルト値を組み合わせた高度な分割代入
- for...of と for...in — 配列には for...of、オブジェクトには for...in
これらのモダン構文を使いこなすことで、より安全で読みやすいJavaScriptが書けるようになります。特にオプショナルチェーニング(?.)とNull合体演算子(??)の組み合わせは、APIレスポンスの処理で毎日のように使うパターンです。ぜひ実際のコードで積極的に活用してみてください。