モダンJavaScriptの基礎:let/const・アロー関数・Promise・async/awaitをコード例付きで解説
JavaScriptの新しい書き方、なんとなく見たことはあるけど、ちゃんと理解できていない…そんなあなたへ。
この記事では、モダンJavaScriptで押さえるべき「let/const」「アロー関数」「Promise」「async/await」の基本を、実用コード例付きでやさしく解説します。
※ブラウザのDevToolsやCodeSandboxでそのまま動作するコードを載せたので、ぜひ手を動かしながら読んでみてください。
モダンJavaScriptとは?—定義とES6 / ESNextの歩み
「モダンJavaScript」という言葉はやや曖昧ですが、この記事ではES6(ECMAScript 2015)以降の機能を“ビルドツールを介さず書ける構文”と定義します。
ES6でlet/const、クラス構文、テンプレートリテラルなどが追加され、以降は毎年6月にESNext仕様(ES2016, ES2017…ES2025)が承認され続けています。
BabelやTypeScriptなどのトランスパイラが開発現場で標準的に使われるようになったため、最新構文を古いブラウザに自動変換して動かすワークフローが一般化しました。
加えてChrome 118以降やFirefox 120以降では、主要ES6機能がネイティブ実装済みで、趣味〜小規模プロジェクトならトランスパイルせずとも動かせます。
迷ったら“ES6を土台に、毎年少しずつ追加される新機能をキャッチアップする”という意識で進めましょう。
letとconstで始める安全な変数宣言【varは新規開発では避けよう】
letとconstはブロックスコープを持ち、forループやif文内で宣言しても外に漏れません。下記を実行すると差が一目瞭然です。
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0); // 3 3 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j), 0); // 0 1 2
constは再代入不可なので、状態を変えない不変データの意図を明示できます(プロパティ変更は可能)。
例外を防ぐTDZ(Temporal Dead Zone)も重要で、宣言前にアクセスするとReferenceErrorが投げられます。
たとえば以下のように、let
やconst
で宣言する前に変数を参照するとエラーになります。
console.log(x); // ReferenceError
let x = 10;
この「宣言より前の一時的に無効な時間帯」がTDZです。varにはこの仕組みがないため、未定義のままアクセスできてしまう違いがあります。
既存コードの保守ではvarに遭遇しますが、新規開発では基本的にvarは避け、let/constを選ぶのが業界標準です。ブラウザサポートはIE11を除く全主要ブラウザで完了しています。
コピーによるイミュータブル操作
データを書き換えずに更新するにはスプレッド構文が便利です。
const nums = [1, 2, 3];
const next = [...nums, 4]; // 元の配列には触れない
この思想はReduxやPiniaなどの状態管理ライブラリとも親和性が高く、バグを未然に防ぎます。
アロー関数の短さとthis束縛を使いこなす【書き分けのコツ】
アロー関数の魅力は短いシンタックスとlexical this。外側のthisを自動参照するため、bindやself = thisが不要です。
class Counter {
count = 0;
inc = () => { this.count++; };
}
ただしコンストラクタやprototype継承が必要な場合はfunction宣言が適しています。この“道具の使い分け”こそチーム開発で差がつくポイントです。
イベントリスナー解除の落とし穴
イベントを解除するremoveEventListenerは“同じ関数オブジェクト”を渡さなければ効果がありません。インラインでアロー関数を渡すと次のように解除できなくなります。
button.addEventListener('click', () => console.log('hi'));
// ...
button.removeEventListener('click', () => console.log('hi')); // 無効
回避策として、リスナーを変数に入れてから登録・解除すると安心です。
Promiseで非同期フローを直列化【状態管理とエラー処理】
コールバック地獄を救済したPromiseはpending → fulfilled / rejectedの一方向・不可逆な状態遷移を持ちます。
いったん決定した後は変更できないため、二重resolveバグを防止できます。 エラーはcatchで一括処理できるので、深いネストともお別れです。
fetch('/api/user/1')
.then(r => r.json())
.then(d => console.log(d))
.catch(e => console.error(e));
並列ならPromise.all、どれか一つで十分ならPromise.raceを使いましょう。ブラウザサポートはIE11を除くモダン環境で安定しています。
ベストプラクティス:エラーを握りつぶさない
catchの中でエラーを再スローしないと、呼び出し側で異常を検知できません。迷ったら以下のパターンを覚えておくと安心です。
.catch(err => { console.error(err); throw err; });
async/awaitで同期風に書く【トップレベルawaitも解説】
async関数は常にPromiseを返し、awaitはその完了を待機します。try … catchと組み合わせれば同期風の読みやすさを保ちつつ、例外処理もスマートです。
async function getUser(id) {
try {
const res = await fetch(`/api/user/${id}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
} catch (e) {
console.error(e);
throw e;
}
}
トップレベルawaitはES2022で正式仕様となりましたが、<script>タグ単体では動作しません。必ず<script type="module">
、または.mjs
ファイル内で使う必要があります。
DenoやEdge Workersなどモジュール前提の環境では特に重宝します。
直列・並列を自在に切り替える
依存性がない複数フェッチを最速で終わらせたい場合は、Promise.all内でawaitすると効率的です。
const [posts, comments] = await Promise.all([
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
フレームワークとTypeScriptでの活用例【React / Vueで実践】
Reactではステート更新を防ぐためconst+スプレッドが定番です。以下は簡易Todoコンポーネントの例です。
export const TodoList = () => {
const [items, setItems] = React.useState([]);
const add = async text => {
const newItem = await saveItem(text); // async/await
setItems(prev => [...prev, newItem]); // イミュータブル更新
};
return <button onClick={() => add('sample')}>追加</button>;
};
Vue 3 + TypeScriptではsetup関数内でPromise/awaitが書きやすく、型安全にAPIレスポンスを扱えます。
const { data } = await useFetch<User[]>('/api/users');
TypeScriptは型注釈でcatch漏れを検出できるため、let/constの誤用やnullチェック忘れをコンパイル時に防げます。モダン構文+静的型付けの組み合わせは、可読性と安全性を同時に高める強力な武器です。
たとえば、useFetch<User[]>('/api/users')
のようにジェネリクスで型注釈を加えることで、APIレスポンスの構造も明確になり、補完や静的チェックが一段と強化されます。
学習ロードマップとまとめ【次の一歩】
学習順序の目安は以下のとおりです。
- let / const: 安全な変数宣言の基本
- アロー関数: 短い構文とthisの扱いに慣れる
- Promise: 非同期処理の基礎
- async / await: 非同期を直感的に書ける構文
まず変数宣言と関数定義をモダン構文に置き換え、次に非同期処理を段階的に習得すると理解がスムーズになります。
そのうえで、オプショナルチェイニング(?.)やNullish Coalescing(??)などES2020以降の糖衣構文を追加で押さえると、実務でも即戦力です。
各機能のブラウザ対応はChrome・Edge・Firefox・Safariの最新バージョンで完了しており、「IE11対応が必要かどうか」がトランスパイル有無の判断基準になります。
最後に、今回のコード例をCtrl+Shift+Iで開いたDevToolsやCodeSandboxに貼り付け、実際の挙動を観察してみてください。自らの手を動かすことが最短の上達法です。
モダンJavaScriptの基礎が固まれば、ReactやVue、Next.js、さらにはDenoやCloudflare Workersまで、学習曲線は一気に緩やかになります。今日から一歩踏み出しましょう。