🌟 モダンJavaScript:ECMAScript 6(ES6、またはES2015)は、2015年に発表されたJavaScript標準の大きなアップデート
Study Roomの資料は、keysubがAIを活用して作成・編集しています。
📜 1. ES6~7からES12までの更新履歴(クリックして展開)
1. es6
- 新しい変数宣言方法: 従来の
varに代わり、ブロックスコープを持つletと定数宣言用のconstが追加され、変数をより安全に管理できるようになりました。 - テンプレートリテラル(Template Literals): バッククォート(`)を使って、文字列内に変数を簡単に埋め込んだり、改行を表現したりできるようになりました。
- 分割代入(Destructuring): オブジェクトや配列から必要な値だけを簡潔に取り出し、変数に代入できます。
- アロー関数(Arrow Functions):
=>構文で関数をより簡潔に宣言でき、thisの束縛は静的に決まります。 - クラス(Classes): オブジェクト指向プログラミングをより直感的に実装できるクラス構文が導入されました。
- Promise: コールバック地獄を解消するためのPromiseオブジェクトが標準に含まれました。
- スプレッド構文(...): 元の配列やオブジェクトを変更せず(不変性を保って)、新しいコピーを作成したり結合したりするときに使います。
- モジュール(import / export): コードを複数のファイルに分割して組み立てる、現代のJavaScriptエコシステムの標準です。
importとexportでコードを独立したモジュールに分割・管理するネイティブ機能が導入されました。 - Mapオブジェクト
- Setオブジェクト
2. es7
- Array.includes()(配列に特定の要素が含まれるかを確認)
3. ES8 (ES2017)
- async / await(非同期処理の標準)
- Object.values() / Object.entries():オブジェクトの値だけを配列にしたり、キーと値を同時に取り出してループ処理したりするときによく使います。
6. ES11 (ES2020)
うんざりするエラーを防ぐ この2つは、実務でTypeErrorによって画面が停止するのを防いでくれる頼もしい機能です。
- オプショナルチェーン(?.): ネストしたオブジェクトを参照するとき、中間の値が
nullまたはundefinedでもエラーを出さず、安全にundefinedを返します。(例:user?.profile?.address) - Null合体演算子(??): 左辺が
nullまたはundefinedの場合にのみ右辺の既定値を使います。数値の0まで偽と判定する従来の||演算子の問題を解決します。 - Promise.allSettled()(すべてのPromiseの結果を配列で返す)
7. ES12 (ES2021)
- String.replaceAll()(文字列内のすべてのパターンを置換)
- 以前は、単語のすべての出現箇所を置換するには正規表現 (
/単語/g) が必要でした。この方法により、すべての出現箇所の置換が直感的かつ簡潔に行えるようになりました。
💡 変更点を中心に概念を整理
📦 1. 変数
1.1 変数宣言
var、let、constの違いは、主に再代入、再宣言、スコープ(有効範囲)にあります。
現代のJavaScriptではvarはほとんど使いません。
[主な相違点の要約]
区別 var let const
再代入 可能 可能 不可(定数)
重複宣言 可能(危険) 不可 不可
スコープ 関数単位(Function) ブロック単位({ }) ブロック単位({ })
ホイスト 宣言前の使用可能(undefined) 宣言前の使用不可(エラー) 宣言前の使用不可(エラー)
1.2 例
🚨 1.2.1 再宣言
var user = "チョルス";
var user = "ヨンヒ";
console.log(user); // "ヨンヒ"
// [let / const] - 重複宣言不可
let score = 100;
// let score = 200; // エラー発生: SyntaxError (すでに宣言済みの変数である)
const ID = "admin";
// const ID = "guest"; // エラー発生: SyntaxError (すでに宣言済みの変数)
const ID_OBJ = {id1: 1, id2: 2};
ID_OBJ.id1 = 3;
console.log(ID_OBJ); // Object {id1: 3, id2: 2} // constで宣言しても、オブジェクトや配列の場合はプロパティやプロパティの値を修正可能
🛡️ 1.2.2 スコープ 関数内で宣言したvarは関数の外に出なければどこでも使えますが、letとconstは自分を囲む中括弧 { }(ブロック)から決して出られません。
varは関数の外に出なければどこでも使えますが、letとconstは自分を囲む中括弧 { }(ブロック)から決して出られません。if (true) {
var varName = "I am var";
}
console.log(varName); // "I am var" (ブロックの外からもアクセス可能)
// [let / const] - ブロックスコープ
if (true) {
let letName = "I am let";
const constName = "I am const";
console.log(letName); // "I am let" (内部では当然可能)
}
1.3 データ型
💎 1.3.1 Symbol:決して重複しない唯一無二の識別子。
- 一意性:
Symbol()で生成した値は、説明が同じでもそれぞれ異なる値として扱われます。 - 不変性: 一度生成すると変更できません。
- 隠蔽性: 通常のループ(
for...in)やObject.keys()では取得されないため、オブジェクトの隠しプロパティを作るときに便利です。
var sym = Symbol();
// 1. シンボルの生成(括弧内の文字列は単なる"説明"です)
const sym1 = Symbol('id');
const sym2 = Symbol('id');
console.log(sym1 === sym2); // false(説明が同じでも値は異なる)
// 2. オブジェクトのキーとして使用
const user = {
name: "John",
[sym1]: 123 // シンボルをキーとして割り当て
};
console.log(user[sym1]); // 123
// 3. 隠蔽性の確認(一般的な方法ではキーが表示されない)
for (let key in user) {
console.log(key); // "name"のみが出力され、sym1はスキップされる
}
console.log(Object.keys(user)); // ["name"]
- オブジェクトを作るとき、キーの位置に角括弧[]を使うと、その中の式をコードとして実行した結果がキーになります。(これもモダンJavaScriptの機能です。)
💬 1.3.2 テンプレートリテラル
1.3.2.1 従来の引用符(' '、" ")の代わりにバッククォート(`)を使います。
1.3.2.2 プレースホルダー:${...}構文を使って、文字列の途中に変数や式を直接挿入します。
const name = "Jquery";
const version = "1.0";
// 1. 従来の方法 (ES5) - 引用符と + 演算子の使用
const oldWay = "現在のライブラリは " + name + " で、バージョンは " + version + " です。";
// 2. テンプレートリテラル方式 (ES6) - バックティックと ${} を使用
const newWay = `現在のライブラリは ${name} で、バージョンは ${version} です。`;
// 3. 改行の例
const multiLine = `
これは
複数行を
そのまま出力する例です。
`;
console.log(newWay);
console.log(multiLine);
1.3.2.3 使用例
// 1. 従来の方法
var html = "<div class='user'>" +
"\n <h1>名前: " + this.name + "</h1>" +
"\n <p>年齢: " + this.age + "</p>" +
"\n</div>";
// 2. テンプレートリテラル
const htmlTemplate = `
<div class="user">
<h1>名前: ${this.name}</h1>
<p>年齢: ${this.age}</p>
</div>
`;
🧩 2. オブジェクト:ES6では、オブジェクトをより簡潔に書けるプロパティ短縮記法と計算済みプロパティが導入されました。
2.1 オブジェクトの基本概念
📝 2.1.1 オブジェクトリテラルでオブジェクトを作成
var me = {name : "keysub", age: 11};
🔍 2.1.2 オブジェクト内の値を読み書きする
me.nameまたはme["age"]
➕ 2.1.3 存在しないプロパティ名に値を代入すると、新しいプロパティが追加されます:
me.hobby = "水泳";
❓ 2.1.4 in演算子でプロパティの有無を確認:
console.log("hobby" in me); // true
2.2 オブジェクトとコレクション(配列、Set、Map)
✂️ 2.2.1 プロパティの短縮記法
変数名とオブジェクトのキー名が同じなら、一度だけ書けば十分です。
const name = "ヨンヒ";
const age = 30;
// ES5
const user5 = { name: name, age: age };
// ES6 (省略形)
const user6 = { name, age };
console.log(user6); // { name: "ヨンヒ", age: 30 }
⚡ 2.2.2 メソッドの短縮記法
関数を定義するときにfunctionキーワードを省略できます。
const dog = {
name: "ワンちゃん",
// ES5: bark: function() { ... }
bark() {
console.log("ワンワン!");
}
};
🧮 2.2.3 計算済みプロパティ名
変数や式を使って、オブジェクトのキー名を動的に設定できます。
const prefix = "user";
const i = 1;
const member = {
[`${prefix}_${i}`]: "ホン・ギルドン" // "user_1" というキーが生成される
};
console.log(member.user_1); // "ホン・ギルドン"
2.3 Set(重複を許さない入れ物)
🚫 2.3.1 従来の配列(Array)とSetの違い
以前は複数のデータを格納するとき、必ず配列(Array)を使っていました。しかし配列は重複を許すため、「重複データをすべて削除して」という要件があるとコードがかなり複雑になりました。 Setは設計上、重複を一切許さない入れ物です。同じ値を何度追加しても、1つだけ残ります。
🛠️ 2.3.2 Setの作成(コンストラクター)
Setの作成方法はとても簡単です。newキーワードを使います。
① 空の入れ物を作成
const mySet = new Set();
② 最初からデータを入れて作成(最もよく使う方法!) 配列などの「反復可能(iterable)」なデータをコンストラクターに渡すと、重複を取り除いてSetを作成します。
// 数字の2と3が重複して含まれている配列を入れてみます。
const numbers = new Set([1, 2, 2, 3, 3, 3, 4]);
console.log(numbers);
// 出力: Set(4) { 1, 2, 3, 4 } <- 重複していた2と3がきれいに1つずつだけ残りました!
🪄 2.3.3 Setの便利な機能(メソッドとプロパティ)
Setには、データを扱うための直感的なメソッドが用意されています。
① データを追加(add)
mySet.add("りんご");
mySet.add("バナナ");
mySet.add("りんご"); // すでに"りんご"があるので無視されます!
console.log(mySet); // 出力: Set(2) { "りんご", "バナナ" }
💡 ヒント:mySet.add(1).add(2);のように、チェーン(数珠つなぎ)で呼び出すこともできます。
② データを確認(has)
入れ物に特定の値があるかを調べます。(配列のincludesと同じ役割ですが、はるかに高速です!)
console.log(mySet.has("リンゴ")); // 出力: true
console.log(mySet.has("スイカ")); // 出力: false
③ データを削除(delete / clear)
mySet.delete("バナナ"); // "バナナ"だけを取り出して捨てる
mySet.clear(); // 袋の中身をすべて空にする!(初期化)
④ サイズを確認(sizeプロパティ)
配列では長さを調べるのに.lengthを使いますが、Setでは必ず.sizeを使います。
const animalSet = new Set(["犬", "猫", "ウサギ"]);
console.log(animalSet.size); // 出力: 3
🎯 2.3.4 実務でSetを使うベストな場面
実務でSetを最もよく使うのはいつでしょうか。配列の重複を取り除くときです。
以前は配列の重複を取り除くために複雑なfilterやループが必要でしたが、Setとスプレッド構文(...)を組み合わせれば1行で済みます。
const dirtyArray = ["ホン・ギルドン", "イ・スンシン", "ホン・ギルドン", "ユ・グァンスン", "イ・スンシン"];
// 1. 配列をSetに変換して重複を排除し、
// 2. 再び角括弧 [] と ... を使って配列としてパッケージ化します。
const cleanArray = [...new Set(dirtyArray)];
console.log(cleanArray);
// 出力: [ "ホン・ギルドン", "イ・スンシン", "ユ・グァンスン" ]
2.4 Map(キーと値をペアで保管するロッカー)
💡 2.4.1 Mapが導入された背景
JavaScriptでは通常のオブジェクト({})もキーと値を保存するロッカーの役割を果たしますが、Mapはオブジェクトの欠点を補うために作られたアップグレード版のロッカーです。
🗄️ 2.4.2 Mapの使い方
2.4.2.1 Mapの作成
① 空のロッカーを作成
const myMap = new Map();
② 最初からデータを入れて作成
Mapではキーと値がペアである必要があるため、配列の中に[key, value]形式の小さな配列を入れて初期化します。
const userMap = new Map([
["name", "ホン・ギルドン"],
["age", 25],
["role", "admin"]
]);
2.4.2.2 Mapを扱う基本メソッド
データを追加・更新(set)
ロッカー(Key)に品物(Value)を入れます。同じキーがすでにあれば、新しい値で上書き(更新)します。
myMap.set("color", "red");
myMap.set("size", 100);
myMap.set("color", "blue"); // "color" キーの値が "red" から "blue" に変更!
// Setと同様にチェーン操作も可能です。
myMap.set("a", 1).set("b", 2);
データを取り出す(get)
キーを渡すと対応する値が返ります。存在しないキーの場合はundefinedが返ります。
console.log(myMap.get("color")); // 出力: "blue"
データを確認(has)
そのキーが存在するかを確認します。(値に関係なく、キーだけを調べます。)
console.log(myMap.has("size")); // 出力: true
データの削除とサイズの確認(delete、clear、size)
myMap.delete("size"); // "size"というキーとその値を削除
console.log(myMap.size); // 現在格納されているデータペアの数を確認
myMap.clear(); // マップ全体を空にする
💡 2.4.2.3 なぜオブジェクト({})ではなくMapを使うのでしょうか?
① どんな値でもキーにできます!(最大の違い) 通常のオブジェクトでは、キーは必ず文字列(String)でなければなりません。数値を指定しても内部で文字列に変換されます。 一方Mapでは、数値、真偽値(true/false)、さらには関数やほかのオブジェクトもキーとして使えます。
const map = new Map();
// 1. 数字をキーとして使用
map.set(1, "数字の1です");
// 2. 別のオブジェクトをキーとして使用(通常のオブジェクト {} では絶対に不可能な魔法!)
const kim = { id: 101, name: "キム・チョルス" };
map.set(kim, "このユーザーはVIP会員です。");
console.log(map.get(kim)); // 出力: "このユーザーはVIP会員です。"
② データ数をすぐに確認できます。
通常のオブジェクトでは、データ数を知るためにObject.keys(obj).lengthのような計算が必要ですが、Mapならmap.sizeだけで済みます。
③ 追加した順序を確実に記憶します。 通常のオブジェクトでは、あとからループすると追加した順番で取り出せる保証がありません。しかしMapでは、必ず追加した順番どおりに取り出せます。
🗝️ 2.5 Object.keys、values、entries:オブジェクトのデータを配列に変換して扱う
2.5.1 Object.keys():オブジェクトのキーだけを配列に取り出す
🔑 オブジェクトのプロパティを確認したり、キーを使ってループしたりするときに便利です。
const user = {
name: "ソン・フンミン",
age: 32,
team: "トッテナム"
};
const keys = Object.keys(user);
console.log(keys);
// 出力: ["name", "age", "team"]
// 応用: キー配列を使ってループを実行する
keys.forEach(key => {
console.log(`${key}: ${user[key]}`);
});
2.5.2 Object.values():オブジェクトの値だけを配列に取り出す(ES8)
💎 キー名は不要で、中に格納されたデータだけが必要な場合に使います。
const scores = {
korean: 90,
english: 85,
math: 100
};
const values = Object.values(scores);
console.log(values);
// 出力: [90, 85, 100]
// 応用: 得点の合計を算出する
const totalScore = values.reduce((acc, curr) => acc + curr, 0);
console.log(`合計点: ${totalScore}`); // 合計点: 275
2.5.3 Object.entries():[Key, Value]形式のキーと値のペアに変換(ES8)
👯 オブジェクトを[key, value]形式の2次元配列に変換します。Mapオブジェクトへの変換や複雑な加工に欠かせません。
const user = {
id: 1,
role: "admin",
status: "active"
};
const entries = Object.entries(user);
console.log(entries);
/* 出力:
[
["id", 1],
["role", "admin"],
["status", "active"]
]
*/
// 応用: for...of文と構造分解代入の組み合わせ (実務で推奨🌟)
for (const [key, value] of Object.entries(user)) {
console.log(`${key} フィールドの値は ${value} です。`);
}
2.5.4 実務のヒント:データをオブジェクトに戻す(Object.fromEntries)
🔄 配列形式に変換したデータを、再びオブジェクトに戻すときに使います。
const arr = [
["name", "イ・ガンイン"],
['age', 23]
];
const obj = Object.fromEntries(arr);
console.log(obj);
// 出力: { name: "イ・ガンイン", age: 23 }
🔍 2.6 includes:特定の値が含まれるか確認(Array & String)
2.6.1 配列でのincludes(Array.prototype.includes)- ES7
🍎 従来の複雑なindexOf() !== -1を置き換え、true / falseを返します。
indexOf() !== -1を置き換え、true / falseを返します。const fruits = ['apple', 'banana', 'mango'];
// [従来のES5方式]
// indexOfは、値が見つかった場合はインデックス番号(0、1、2...)を、見つからなかった場合は-1を返していました。
// そのため、常に-1ではないかを確認する煩わしいコードが必要でした。
if (fruits.indexOf('banana') !== -1) {
console.log("バナナがあります!");
}
// [最新のES7方式] 🌟
// 単に"これ含まれてる?"と尋ねると、すっきりとtrue/falseで答えてくれます。
if (fruits.includes('banana')) {
console.log("バナナがあります!");
}
// 💡 (重要な違い) NaN(Not a Number)の検索
// indexOfはNaNを見つけられませんが、includesは正確に見つけ出します。
const arr = [1, 2, NaN];
console.log(arr.indexOf(NaN)); // -1 (見つからない)
console.log(arr.includes(NaN)); // true (見つかる)
2.6.2 文字列でのincludes(String.prototype.includes)- ES6
📝 長いテキスト(文字列)に特定の単語が含まれているかも簡単に確認できます。
const sentence = "こんにちは、JavaScriptを勉強中です。";
console.log(sentence.includes("JavaScript")); // true
console.log(sentence.includes("Python")); // false
// 💡 ヒント:2番目の引数で"検索を開始する位置"を指定できます。
// インデックス10以降から"こんにちは"があるか検索します。
console.log(sentence.includes("こんにちは", 10)); // false
2.6.3 実務での活用:複数のOR条件をすっきり書く
✂️ if文の||(OR)条件が長くなりすぎたら、配列とincludesを使うとコードを大幅に短くできます。
if文の||(OR)条件が長くなりすぎたら、配列とincludesを使うとコードを大幅に短くできます。const myFruit = 'apple';
// 🚨 [悪い例] 条件が増えるほどコードが無限に長くなる
if (myFruit === 'apple' || myFruit === 'banana' || myFruit === 'mango') {
console.log("フルーツバスケットに入っている果物です。");
}
// ✅ [includesの活用] チェックする項目を配列にまとめてしまう(実務で推奨🌟)
const bucket = ['apple', 'banana', 'mango', 'grape', 'orange'];
if (bucket.includes(myFruit)) {
console.log("フルーツバスケットに入っている果物です。");
}
✂️ 3. 分割代入(Destructuring)
3.1 オブジェクトの分割代入:
オブジェクトからデータを取り出すときは、キー名を基準に変数を作ります。
const user = {
name: "ソン・フンミン",
age: 32,
team: "トッテナム"
};
// [ES5方式] 変数を一つずつ作成して代入する
var name = user.name;
var age = user.age;
// [ES6方式] 一度に変数を生成して代入する
const { name, age, team } = user;
console.log(name); // "ソン・フンミン"
console.log(team); // "トッテナム"
// もし変数名を変更したい場合は?(名前: 新しい名前)
const { name: userName, age: userAge } = user;
console.log(userName); // "ソン・フンミン"
3.2 配列の分割代入:
配列にはキーがないため、順序(インデックス)を基準に変数を作ります。
const colors = ["Red", "Green", "Blue"];
// [ES5方式] 変数を一つずつ作成して代入する
var first = colors[0];
var second = colors[1];
var third = colors[2];
// [ES6方式] 角括弧 [] を使用する
const [first, second, third] = colors;
console.log(first); // "Red"
console.log(second); // "Green"
// 特定の順序をスキップしたい場合(コンマを活用)
const [first_color , , third_color] = colors;
console.log(third_color); // "Blue"
3.3 実務で役立つ場面
💼 3.3.1 関数の引数としてオブジェクトを渡す
オブジェクト全体を受け取ってドット(.)でアクセスする必要はなく、必要なデータだけを直接指定できます。
function printUser({ name, age }) {
console.log(`名前は ${name}、年齢は ${age} です。`);
}
const person = { name: "イ・ガンイン", age: 23, city: "パリ" };
printUser(person); // "名前はイ・ガンイン、年齢は23です。"
🛡️ 3.3.2 既定値(Default Value)の設定:
オブジェクトに該当するデータがない場合に使う既定値を指定できます。
const { name, hobby = "サッカー" } = { name: "キム・ミンジェ" };
console.log(hobby); // オブジェクトにhobbyがないため、デフォルト値"サッカー"が出力される
🛠️ 4. 関数
4.1 関数宣言で関数を作成:
functionキーワードの後に必ず関数名を書きます。(特徴:ホイスティングが適用されます。)
functionキーワードの後に必ず関数名を書きます。(特徴:ホイスティングが適用されます。)sayHello(); // 実行可能!(ホイスト)
function sayHello() {
console.log("こんにちは!");
}
4.2 関数リテラル(関数式、Function Expression)
関数を値のように変数へ代入する方法です。(特徴:ホイスティングされません。)
// sayHi(); // エラー発生!
const sayHi = function() {
console.log("はじめまして!");
};
sayHi(); // 宣言後にのみ呼び出し可能
4.3 アロー関数
4.3.1 関数式との違い
4.3.1.1 🦕 従来の方法(関数式)
const add = function(a, b) {
return a + b;
};
4.3.1.2 🚀 アロー関数
const add = (a, b) => {
return a + b;
};
// 引数が1つの場合は () を省略可能
const add1 = a => { return a + 1; };
// 引数がない場合は () を省略できない
const add2 = () => { return 1 + 2; };
// 関数内の文が return だけの場合、中括弧と return を省略可能
const add3 = a => a + 1;
// 関数内の文が return だけの場合でも、戻り値がオブジェクトリテラルである場合は括弧 () で囲む必要がある
const f = (a, b) => ({ x: a, y: b });
🌟 4.3.2 内部動作の本当の違い:thisの決まり方(重要 🌟)
thisの決まり方(重要 🌟)4.3.2.1 🦕 従来の関数のthis:「誰が呼び出したのか?」(動的束縛)
従来の関数では、実行時に誰が呼び出したかによってthisが変わります。
const user = {
name: "ホン・ギルドン",
sayHi: function() {
console.log(this.name); // 正常に出力:ホン・ギルドン
// 🚨 問題発生:1秒後に挨拶する関数を内部で作成
setTimeout(function() {
// この内部関数は、ブラウザ(window)によって呼び出されたものとみなされるため、thisを失ってしまいます。
console.log(`1秒後: こんにちは、私は ${this.name} です`);
}, 1000);
}
};
user.sayHi();
// 出力: ホン・ギルドン
// 1秒後の出力: こんにちは、私はundefinedです <-- 💥 エラー発生!thisを失う!
4.3.2.2 🚀 アロー関数のthis:「生まれた場所はどこ?」(レキシカル束縛)
アロー関数は、作成された外側の環境(生まれた場所)のthisをそのまま使います。
const user = {
name: "ホン・ギルドン",
sayHi: function() {
console.log(this.name); // 正常に出力:ホン・ギルドン
// ✅ 解決:矢印関数の使用
setTimeout(() => {
// 矢印関数は、外側のsayHiのthis(user)をそのまま引き継ぎます。
console.log(`1秒後: こんにちは、私は ${this.name} です`);
}, 1000);
}
};
user.sayHi();
// 出力: ホン・ギルドン
// 1秒後に出力:こんにちは、僕はホン・ギルドンです <-- 🎉 完璧に動作!
🛠️ 4.3.3 アロー関数の活用
📦 シナリオ: サーバーから取得した顧客データ(Raw Data)
const users = [
{ id: 1, name: "ホン・ギルドン", age: 25, isVip: true },
{ id: 2, name: "李舜臣", age: 40, isVip: false },
{ id: 3, name: "柳寛順", age: 18, isVip: true },
{ id: 4, name: "カン・ガムチャン", age: 35, isVip: false }
];
1. map:「データ加工工場」(形を変える)
// users配列を順に処理し、各ユーザー(u)の名前(u.name)のみを抽出して新しい配列を作成します。
const names = users.map(u => u.name);
console.log(names);
// 出力: ["ホン・ギルドン", "イ・スンシン", "ユ・グァンスン", "カン・ガムチャン"]
2. filter:「データのふるい」(条件に合うものだけを抽出)
// users配列を順に処理し、isVipがtrueのユーザーのみを通過させます。
const vipUsers = users.filter(u => u.isVip === true);
console.log(vipUsers);
/* 出力:
[
{ id: 1, name: "ホン・ギルドン", age: 25, isVip: true },
{ id: 3, name: "ユ・グァンスン", age: 18, isVip: true }
]
*/
👑 3. 実務で大活躍:チェーン処理
const vipNames = users
.filter(u => u.isVip) // ステップ1:VIPのみを先に絞り込む
.map(u => u.name); // ステップ2:残ったデータから名前だけを抽出する
console.log(vipNames);
// 出力:["ホン・ギルドン", "ユ・グァンスン"]
4.4 残余引数(Rest Parameter):
数が決まっていない引数を1つの配列にまとめる構文
4.4.1 基本例: 関数の引数を配列として受け取る
function sumAll(...numbers) {
// numbersは[1, 2, 3, 4, 5]という配列になります。
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sumAll(1, 2)); // 3
console.log(sumAll(1, 2, 3, 4, 5)); // 15
4.4.2 通常の引数と一緒に使う(必ず最後に置きます)
function showGrade(name, ...scores) {
console.log(`${name}さんの得点数: ${scores.length}個`);
console.log(scores); // [90, 85, 100]
}
showGrade("チョルス", 90, 85, 100);
4.4.3 分割代入でのRest
// ① 配列での活用
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2, 3, 4, 5]
// ② オブジェクトでの活用
const user = { id: 1, name: "ヨンヒ", age: 20, city: "ソウル" };
const { id, ...userInfo } = user;
console.log(id); // 1
console.log(userInfo); // { name: "ヨンヒ", age: 20, city: "ソウル" }
4.5 スプレッド構文(Spread):
配列やオブジェクトという「入れ物」を展開し、中身を1つずつ取り出す構文です。
4.5.1 配列でのスプレッド構文
const parts = ["肩", "膝"];
const body = ["頭", ...parts, "足"];
console.log(body); // ["頭", "肩", "膝", "足"]
// 配列のコピー(まったく新しい配列を作成)
const original = [1, 2, 3];
const copy = [...original];
4.5.2 オブジェクトでのスプレッド構文
const user = { name: "チョルス", age: 20 };
const updatedUser = { ...user, city: "ソウル" };
console.log(updatedUser); // { name: "チョルス", age: 20, city: "ソウル" }
4.5.3 Rest(まとめる)とSpread(展開する)の違い
見た目は同じ...ですが、使い方は正反対です。
* Rest: 複数の引数を受け取り、1つの配列にまとめます。(関数のパラメーター部分)
* Spread: 1つの配列/オブジェクトを複数の値に分解します。(関数呼び出しや宣言の外)
4.6 シャローコピー(浅いコピー)とディープコピー(深いコピー)
📄 4.6.1 シャローコピー(浅いコピー)
オブジェクトの最上位だけをコピーし、内部のネストされたオブジェクトは参照(アドレス)だけをコピーします。
const original = {
name: "チョルス",
skills: ["JS", "HTML"] // ネストされた配列(オブジェクト)
};
const copy = { ...original }; // 浅いコピー
copy.name = "ヨンヒ"; // 1段階目の値の変更
copy.skills.push("CSS"); // 2段階目(ネストされたオブジェクト)の値の変更
console.log(original.name); // "チョルス" (1段階目には影響なし)
console.log(original.skills); // ["JS", "HTML", "CSS"] (オリジナルも一緒に変更された!)
📑 4.6.2 ディープコピー(深いコピー)
オブジェクトのすべての階層を新しいメモリー領域に完全にコピーします。(元のオブジェクトとコピーは完全に分離されます。)
const original = {
name: "チョルス",
skills: ["JS", "HTML"]
};
// 最新のブラウザ標準方式 (推奨) structuredClone を使用
const deepCopy = structuredClone(original);
deepCopy.skills.push("React");
console.log(original.skills); // ["JS", "HTML"] (オリジナルは保持!)
console.log(deepCopy.skills); // ["JS", "HTML", "React"] (コピーのみ変更)
4.7 クロージャ(参考):
🎒 関数が生まれた場所の環境(変数)を覚えている「バックパック」を背負っている状態
4.7.1 クロージャのない通常の世界
function sayHello() {
let name = "ホン・ギルドン"; // 関数内で変数を生成
console.log(name);
}
sayHello(); // "ホン・ギルドン" を出力
// この時点で関数の実行が終了したため、nameという変数は完全に破棄されます。
4.7.2 関数内に内部関数を作って外に返すと、変数が維持される
function makeCounter() {
let count = 0; // 1. 親関数(外部関数)の変数
// 2. 内部関数を作成し、外部へ返します(return)。
return function() {
count = count + 1; // 3. ホームの変数であるcountを使用します。
return count;
};
}
const myCounter = makeCounter();
// makeCounter()の実行は終了し、count変数は破棄されるはずですが、引き続き動作します!
console.log(myCounter()); // 出力: 1
console.log(myCounter()); // 出力: 2
console.log(myCounter()); // 出力: 3
🏗️ 5. クラス(Class)の導入
5.1 ES5:コンストラクター関数とnew演算子
⚙️ 5.1.1 動作の仕組み:
function Person(name, age) {
this.name = name;
this.age = age;
}
// メソッドはprototypeに明示的に追加
Person.prototype.sayHello = function() {
console.log("こんにちは、私の名前は " + this.name + "です。");
};
// newを使用してインスタンスを作成
var user1 = new Person("キム・チョルス", 25);
user1.sayHello(); // 出力: こんにちは、私の名前はキム・チョルスです。
5.2 ES6:classキーワードの導入
🚀 5.2.1 動作の仕組み:
class Person {
// [要素 2: プロパティ] 外部に絶対に公開してはならないデータ(プライベートフィールド)
#name;
// [要素 1: コンストラクタ] プロパティの初期化
constructor(name) {
// これは this.#name 変数に直接値を代入するのではなく、セッターを呼び出しているものである
this.name = name;
}
// [要素 4: ゲッター]
get name() {
return this.#name;
}
// [要素 4: セッター] 外部から名前を変更できるようにしたい場合は、セッターを追加する
set name(value) {
this.#name = value;
}
// [要素 3: メソッド] メソッドはprototypeに自動的に追加される。
sayHello() {
// ここでthis.nameを使用すると、自動的にゲッターが呼び出され、値が取得される
console.log(`こんにちは、私の名前は ${this.name} です。`);
}
}
// インスタンスの生成方法は同じ
const user = new Person("イ・ヨンヒ");
user.sayHello(); // 出力:こんにちは、私の名前はイ・ヨンヒです。
// セッターの呼び出し
user.name = "チョルス";
// ゲッターの呼び出し
console.log(user.name); // 出力:チョルス
// プライベートフィールドへの直接アクセスを試みる(クラスの外からは不可)
// console.log(user.#name); // 💥 エラー発生!
📦 6. モジュール(Module):ファイル単位でコードを分割・結合する構文(import / export)
6.1 名前付きエクスポート&インポート(複数の名前付き要素をexport / import)
🧩 1つのファイルから複数の関数や変数をexportするときに使います。元の名前のまま、中括弧 { } を使ってimportする必要があります。
[math.js] - 部品(モジュール)を作るファイル
// 宣言の前に export を付けて外部にエクスポートします。
export const PI = 3.141592;
export function add(a, b) {
return a + b;
}
export const multiply = (a, b) => a * b;
// 💡 (参考) ファイルの最後でまとめてエクスポートすることもできます。
// export { PI, add, multiply };
[app.js] - 部品をimportして使うファイル
// 構造分解代入のように、必要なものだけを中括弧 {} で囲んで取得します。
import { PI, add } from './math.js';
console.log(PI); // 3.141592
console.log(add(10, 20)); // 30
// 💡 インポート時に名前を変更したい場合は、"as" キーワードを使用します。
import { multiply as multi } from './math.js';
console.log(multi(2, 5)); // 10
6.2 デフォルトエクスポート&インポート(ファイルの代表を1つだけexport / import)
👑 ファイルのメインとなるクラスや関数が1つの場合に使います。中括弧 { } を使わず、好きな名前でimportできます。
[User.js] - 部品を作るファイル
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`こんにちは、${this.name}です。`);
}
}
// このファイルのデフォルトモジュールとして User クラスを指定します。
export default User;
[main.js] - 部品をimportして使うファイル
// デフォルトでエクスポートされたモジュールは、括弧なしで任意の名前でインポートできます。
import Customer from "./User.js"; // UserをCustomerという名前でインポート
const user1 = new Customer("ホン・ギルドン");
user1.greet(); // こんにちは、ホン・ギルドンです。
6.3 *(ワイルドカード)を使って一括import
📦 exportされたモジュールが多く、1つずつ書くのが面倒なときは、ファイル全体を1つのオブジェクトとして読み込みます。
[app.js]
// math.jsのすべてのexportをインポートし、"MathUtils"という単一のオブジェクトに格納します。
import * as MathUtils from './math.js';
// オブジェクトのプロパティ(ドット表記)を通じてアクセスします。
console.log(MathUtils.PI);
console.log(MathUtils.add(5, 5));
6.4 HTMLでモジュールを読み込む(重要な注意点 🚨)
🌐 ブラウザー(HTML)でimport / exportを使うには、scriptタグに必ずtype="module"属性を指定します。
type="module"属性を指定します。[index.html]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>モジュールのテスト</title>
</head>
<body>
<script type="module" src="app.js"></script>
</body>
</html>
⏳ 7. Promise(プロミス):コールバック地獄を抜け出す非同期処理の標準オブジェクト
7.1 Promiseの3つの状態(State)
🚦 Promiseは、処理が終わるまで結果を「約束」するオブジェクトです。処理の状態は3つに分かれます。
- Pending(待機): 非同期処理がまだ完了していない状態(約束はしたものの、結果がまだ出ていない)。
- Fulfilled(履行/成功): 非同期処理が正常に完了し、約束された結果を返した状態。
- Rejected(拒否/失敗): 非同期処理に失敗した、またはエラーが発生した状態。
7.2 Promiseの作成(Producer)
🛠️ new Promiseで作成し、resolve(成功時に呼び出す)とreject(失敗時に呼び出す)という2つのコールバック関数を渡します。
new Promiseで作成し、resolve(成功時に呼び出す)とreject(失敗時に呼び出す)という2つのコールバック関数を渡します。// サーバーからデータを取得する状況を想定します。
const fetchData = new Promise((resolve, reject) => {
console.log("データを取得中...");
setTimeout(() => {
const success = true; // 通信の成否(テスト用)
if (success) {
// 1. 成功した場合:resolve()の中に結果データを格納して送信します。(状態:Fulfilled)
resolve({ id: 1, name: "ホン・ギルドン" });
} else {
// 2. 失敗した場合:reject() 内にエラーオブジェクトを格納して送信します。(状態:Rejected)
reject(new Error("ネットワークエラーが発生しました。"));
}
}, 2000); // 2秒かかる処理と仮定
});
7.3 Promiseの利用(Consumer)
🎧 作成したPromiseには、成功時の.then()、失敗時の.catch()、必ず実行するコードの.finally()を指定します。
.then()、失敗時の.catch()、必ず実行するコードの.finally()を指定します。// 上で作成した fetchData(Promise オブジェクト) を使ってみましょう。
fetchData
.then((data) => {
// 1. resolve() が呼び出されると、ここが実行されます。
console.log("✅ 成功:", data); // 出力: ✅ 成功: { id: 1, name: "ホン・ギルドン" }
})
.catch((error) => {
// 2. reject()が呼び出されると、ここが実行されます。(エラー処理)
console.log("❌ 失敗:", error.message);
})
.finally(() => {
// 3. 成功・失敗にかかわらず、最後に必ず一度実行されます。(ローディングスピナーを停止させる際に主に使用)
console.log("🏁 すべての処理が完了しました。");
});
7.4 実務で活躍:Promiseチェーン(Chaining)
⛓️ 以前はコールバック関数を入れ子にする「コールバック地獄(Callback Hell)」がありましたが、Promiseでは.then()を列車のようにつないで簡潔に処理できます。
.then()を列車のようにつないで簡潔に処理できます。// サーバーから番号を取得し → その番号でユーザーを検索し → そのユーザーの投稿を探すという一連の処理
const fetchNumber = new Promise((resolve) => {
setTimeout(() => resolve(1), 1000);
});
fetchNumber
.then(num => {
console.log(`最初の結果: ${num}`); // 1
return num * 10; // return すると、この値が次の .then() に渡されます。(自動的に新しいPromiseになります)
})
.then(num => {
console.log(`2番目の結果: ${num}`); // 10
return num * 10;
})
.then(num => {
console.log(`3番目の結果: ${num}`); // 100
})
.catch(error => {
// 途中でどこでエラーが発生しても、最も近い catch の一つがすべて捕捉します。
console.log("エラー発生!", error);
});
7.5 実務でよく使うPromiseメソッド
📦 複数の非同期処理をまとめて同時に扱うときに便利なメソッドです。
const p1 = new Promise(resolve => setTimeout(() => resolve("1番目のデータ"), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve("2番目のデータ"), 2000));
// 1. Promise.all()
// すべてのプロミスが"成功"するまで待機し、一度に配列として結果を受け取ります。
// (ただし、一つでも失敗すると、全体が即座にcatchブロックに遷移してしまう)
Promise.all([p1, p2])
.then(results => console.log("すべて完了:", results));
// 2秒後に出力:すべて完了: ["1番目のデータ", "2番目のデータ"]
// 2. Promise.race()
// 陸上競技のように、複数の処理のうち"最も早く終わったもの"1つの結果のみを取得します。
Promise.race([p1, p2])
.then(result => console.log("1位:", result));
// 1秒後に出力: 1位: 1番目のデータ
7.6 Promise.allSettled()(すべてのPromiseの結果を配列で返す)
🛡️ Promise.all()の欠点(1つでも失敗すると停止する)を補い、成功・失敗にかかわらずすべての結果を待って、各状態とともに配列で返します。(ES11で導入/実務でもおすすめ 🌟)
const p1 = new Promise(resolve => setTimeout(() => resolve("成功データ"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("失敗エラー"), 2000));
Promise.allSettled([p1, p2])
.then(results => console.log(results));
/*
出力結果(2秒後に配列を返す):
[
{ status: "fulfilled", value: "成功データ" },
{ status: "rejected", reason: "失敗エラー" }
]
*/
🚀 8. async / await:Promiseを同期コードのように読みやすくする仕組み(ES8)
8.1 asyncとawaitの基本ルール
💡 この2つのキーワードだけ覚えれば、非同期コードが驚くほど簡単になります。
async: 関数の前に付けると、その関数は必ずPromiseオブジェクトを返すようになります。await:async関数の中でのみ使えます。Promiseの処理が終わるまでコードの実行を一時停止して待つ役割を果たします。(つまり.then()を置き換えます。)
8.2 Promise(.then)とasync/awaitのコードを比較
🥊 コードを比較して、async/awaitを使う理由を見てみましょう。(サーバーからユーザー情報を取得する場合)
[従来の方法] Promiseチェーン(.thenを使用)
function getUser() {
// fetch()は、サーバーからデータを取得する組み込みのPromise関数です。
fetch('[https://jsonplaceholder.typicode.com/users/1](https://jsonplaceholder.typicode.com/users/1)')
.then(response => response.json())
.then(user => {
console.log("ユーザー名:", user.name);
})
.catch(error => {
console.log("エラー発生:", error);
});
}
[新しい方法] async / awaitを使用 🌟(実務での標準)
// 関数の前にasyncを付けます。
async function getUserAsync() {
// await を使用すると、データが届くまで待機し、届いたら左側の変数に格納します。
const response = await fetch('[https://jsonplaceholder.typicode.com/users/1](https://jsonplaceholder.typicode.com/users/1)');
const user = await response.json();
// コールバック関数(矢印)なしで、上から下へとスムーズに読み進めます!
console.log("ユーザー名:", user.name);
}
getUserAsync();
8.3 エラー処理(try ... catch)
🛡️ async/awaitでは.catch()の代わりに、JavaScriptで従来から使われているエラー処理方法のtry/catch文を使います。
.catch()の代わりに、JavaScriptで従来から使われているエラー処理方法のtry/catch文を使います。async function getUserSafe() {
try {
// 1. 実行するコードを try ブロック内に記述します。
const response = await fetch('https://invalid.example/users/1');
const user = await response.json();
console.log(user);
} catch (error) {
// 2. tryブロック内でエラーが発生しても、コードは停止せず、直ちにcatchブロックに移行します。
console.log("❌ 通信中に問題が発生しました!", error.message);
} finally {
// 3. (オプション) 成功・失敗にかかわらず、最後に実行されます。
console.log("ロード画面終了");
}
}
8.4 実務のヒント:Promise.allと併用(並列処理)
⚡ awaitはコードを待機させます。無関係なデータを取得するときに何も考えずawaitを使うと、処理が遅くなります。
awaitはコードを待機させます。無関係なデータを取得するときに何も考えずawaitを使うと、処理が遅くなります。🚨 遅い方法(逐次処理)
async function getDashboardData() {
// 天気情報を取得するまで2秒待機
const weather = await fetch('/api/weather');
// その後、ニュースを取得するまで2秒待機 (合計4秒かかります)
const news = await fetch('/api/news');
}
🚀 速い方法(並列処理 - Promise.allを活用)
同時に取得できるデータなら、Promise.allと組み合わせて使います。
async function getDashboardDataFast() {
// 2つのリクエストを同時に送信し、両方が完了するまで(await)1回だけ待機します。(合計2秒かかります)
const [weather, news] = await Promise.all([
fetch('/api/weather'),
fetch('/api/news')
]);
console.log("データの読み込み完了!");
}
🛡️ 9. オプショナルチェーン(?.):ネストしたオブジェクトからエラーなく安全に値を取得(ES11)
9.1 以前の厄介なエラー防止方法
💣 オブジェクト内のオブジェクトを参照するとき、中間の値がない(undefined)とTypeErrorが発生し、画面が停止します。
undefined)とTypeErrorが発生し、画面が停止します。const user = {
name: "ホン・ギルドン",
// プロフィール情報が全くないユーザーであると仮定します。
};
// 🚨 [危険] エラー発生! undefined.addressを読み取ろうとしたためです。
// console.log(user.profile.address);
// 🦖 [旧来のES5方式] エラーを防ぐために、&&演算子を連鎖させる必要がありました。
if (user && user.profile && user.profile.address) {
console.log(user.profile.address);
} else {
console.log("住所情報がありません。");
}
9.2 オプショナルチェーン(?.)の導入
🪄 ?.を使えば途中の値がなくてもエラーにならず、何も起こさずにundefinedを返して処理を止めます。
?.を使えば途中の値がなくてもエラーにならず、何も起こさずにundefinedを返して処理を止めます。const user = { name: "ホン・ギルドン" };
// ✅ [最新のES11方式]
// "userの中にprofileはあるかな? (?. ) あればaddressも探して!"
const address = user?.profile?.address;
console.log(address);
// エラーは発生せず、静かに"undefined"を出力します。
9.3 配列と関数での活用
🎯 オブジェクトのプロパティだけでなく、配列のインデックスや関数呼び出しにも安全に使えます。
// 1. 配列の要素に安全にアクセスする
const users = null;
// usersが配列でなかったりnullであっても、エラーなしでundefinedを返す
console.log(users?.[0]?.name);
// 2. 関数(メソッド)を安全に呼び出す
const admin = {
adminPlay() {
console.log("管理者モードを実行!");
}
};
const guest = {};
// adminPlayという関数が存在する場合にのみ実行せよ!
admin.adminPlay?.(); // "管理者モードを実行!"
guest.adminPlay?.(); // エラーなしで静かに処理される
🛟 10. Null合体演算子(??):確実で安全な既定値の設定(ES11)
10.1 従来のOR(||)演算子にあった重大な問題
🚨 以前は値がないときに||で既定値を設定していましたが、数値の0や空文字列("")も「偽(値なし)」と判断してしまう深刻な問題がありました。
||で既定値を設定していましたが、数値の0や空文字列("")も「偽(値なし)」と判断してしまう深刻な問題がありました。// ショッピングモールの商品のカート内の数量を0に設定したと仮定しましょう。
const count = 0;
// 開発者の意図:"countの値が全くない場合は、デフォルト値の10を入れてくれ!"
const result = count || 10;
console.log(result);
// 出力:10(バグ発生💥)
// JavaScriptは数値の0を"false"として扱うため、ユーザーが入力した0を無視してしまいます。
10.2 Null合体演算子(??)による解決
🛡️ ??は値がnullまたはundefinedの場合にだけ右辺の既定値を使います。数値の0や空文字列も有効な値として扱います。
??は値がnullまたはundefinedの場合にだけ右辺の既定値を使います。数値の0や空文字列も有効な値として扱います。const count = 0;
const text = "";
// ✅ [最新のES11方式]
const safeCount = count ?? 10;
const safeText = text ?? "デフォルトのテキスト";
console.log(safeCount); // 出力: 0 (正常に動作! 0を値として認識)
console.log(safeText); // 出力: "" (正常に動作!空文字列も値として認識されます)
// null または undefined の場合にのみ、デフォルト値を返します。
const notExist = null ?? "デフォルト値が動作";
console.log(notExist); // 出力: "デフォルト値が動作"
10.3 実務で便利な組み合わせ:?. と ?? を併用
👑 オプショナルチェーンでエラーを防ぎ、Null合体演算子で既定値も設定する、実務で便利なパターンです。
const user = {
name: "李舜臣",
// profile 情報がない
};
// "ユーザーの居住都市(city)を検索し、途中でエラーが出ないように(?. )、
// もし最終的に値が見つからなかった場合(?? )、"不明"と表示する!"
const userCity = user?.profile?.city ?? "不明";
console.log(`居住地: ${userCity}`);
// 出力: 居住地: 不明
🔄 11. String.replaceAll():文字列内のパターンを一括置換(ES12)
11.1 replace()の限界と従来の方法
🚨 従来のreplace()には、見つかった単語の「最初の1つ」しか置換しないという重大な制約がありました。
replace()には、見つかった単語の「最初の1つ」しか置換しないという重大な制約がありました。const text = "リンゴ、バナナ、リンゴ、ブドウ、リンゴ";
// 1. 従来のreplace()を使用(最初の"リンゴ"のみが"スイカ"に置き換わる)
const result1 = text.replace("リンゴ", "スイカ");
console.log(result1);
// 出力: "スイカ, バナナ, リンゴ, ブドウ, リンゴ"
// 2. [ES11以前] 全体を置換するには、複雑な"正規表現(Regex)"とグローバルフラグ(g)を必ず使用する必要がありました。
const result2 = text.replace(/リンゴ/g, "スイカ");
console.log(result2);
// 出力: "スイカ、バナナ、スイカ、ブドウ、スイカ"
11.2 replaceAll()による解決
✨ 複雑な正規表現の記号(/ /g)を使わなくても、分かりやすい文字列だけで一致する単語をすべて置換できます。
const text = "リンゴ、バナナ、リンゴ、ブドウ、リンゴ";
// ✅ [最新のES12方式] 置き換えたい単語を文字列として指定するだけで完了!
const finalResult = text.replaceAll("リンゴ", "スイカ");
console.log(finalResult);
// 出力: "スイカ、バナナ、スイカ、ブドウ、スイカ"
11.3 実務でのヒント:メソッドチェーンとデータのクレンジング
🧹 ユーザーが入力した汚れたデータ(特殊文字だらけの文字列など)を一度にきれいにするときにとても便利です。
// ユーザーが入力した不揃いな電話番号データ
const dirtyPhone = "010-1234-5678 (本人)";
// "-"記号をすべて削除し、"(本人)"という文字も削除したい場合(チェーニングを使用)
const cleanPhone = dirtyPhone
.replaceAll("-", "") // すべてのハイフンを削除
.replaceAll(" (本人)", ""); // " (本人)" というテキストを削除
console.log(cleanPhone);
// 出力: "01012345678"