JavaScriptの文法解説
基本文法から、クロージャ・プロトタイプベースOOP・非同期処理といった言語特有の仕組み、さらにデータ構造とアルゴリズムの実装まで、基本情報技術者試験の合格を目指せるレベルで解説します。
文法解説
1. コメント
単一行コメントは//、複数行コメントは/* */で書きます。関数の仕様説明には/** */形式のJSDocコメントがよく使われます。
// 単一行コメント
/*
複数行コメント
複数行にわたる説明を書ける
*/
/**
* JSDoc形式のコメント(関数の説明などに使う)
* @param {number} a - 数値
*/
function double(a) {
return a * 2; // 行末コメント
}
2. 変数宣言(var / let / const)とスコープ
varは関数スコープで巻き上げ(hoisting)が起こりやすく、現在はlet(再代入可)・const(再代入不可)のブロックスコープ変数が推奨されます。let/constは宣言前に参照するとTDZ(一時的デッドゾーン)によりエラーになります。
function scopeDemo() {
if (true) {
var functionScoped = "var"; // 関数スコープ
let blockScoped = "let"; // ブロックスコープ
const alsoBlock = "const"; // ブロックスコープ・再代入不可
}
console.log(functionScoped); // "var" ※if の外でも参照できる
// console.log(blockScoped); // ReferenceError(ブロック外)
}
console.log(typeof hoisted); // "undefined"(varは巻き上げられ初期値はundefined)
var hoisted = 10;
// console.log(notYetDefined); // ReferenceError(TDZ: 一時的デッドゾーン)
let notYetDefined = 20;
3. データ型(プリミティブ/オブジェクト、動的型付け)
プリミティブ型は number・string・boolean・null・undefined・symbol・bigint の7種類、それ以外はすべてオブジェクト型(配列・関数も含む)です。JavaScriptは動的型付け言語で、変数の型は代入によって実行時に変化します。
const num = 3.14; // number
const str = "文字列"; // string
const bool = true; // boolean
const nothing = null; // null(意図的に値がないことを示す)
let notSet; // undefined(未代入)
const big = 9007199254740993n; // bigint
const sym = Symbol("id"); // symbol
const arr = [1, 2, 3]; // object(配列)
const obj = { name: "太郎" }; // object
function fn() {} // object(関数もオブジェクトの一種)
console.log(typeof num, typeof str, typeof arr, typeof fn);
// "number" "string" "object" "function"
let dynamic = 10;
dynamic = "文字列に変わった"; // 動的型付け:再代入によって型が変わる
4. 演算子
算術・比較・論理演算子に加え、型変換を行わない厳密等価===、optional chaining(?.)、nullish coalescing(??)などが重要です。??はnull/undefinedの場合だけ既定値を適用する点が||と異なります。
const a = 10, b = 3;
console.log(a + b, a - b, a * b, a / b, a % b, a ** b); // 算術演算子
console.log(1 == "1"); // true(型変換して比較)
console.log(1 === "1"); // false(型まで比較。基本はこちらを使う)
console.log(true && false, true || false, !true); // 論理演算子
const user = { profile: null };
console.log(user.profile?.age); // undefined(optional chaining)
console.log(user.profile?.age ?? 20); // 20(nullish coalescing)
5. 制御構文(分岐・繰り返し)
if/else・switchによる分岐、for・while・do...while・for...of(値を反復)・for...in(キーを反復)による繰り返しが使えます。
const score = 75;
if (score >= 80) {
console.log("優");
} else if (score >= 60) {
console.log("良");
} else {
console.log("再履修");
}
switch (Math.floor(score / 10)) {
case 10:
case 9:
case 8:
console.log("優");
break;
default:
console.log("良以下");
}
for (let i = 0; i < 3; i++) { console.log(i); }
let i = 0;
while (i < 3) { i++; }
do { i--; } while (i > 0);
for (const value of [10, 20, 30]) { console.log(value); } // 値を反復
for (const key in { a: 1, b: 2 }) { console.log(key); } // キーを反復
for (let n = 0; n < 10; n++) {
if (n % 2 === 0) continue;
if (n > 7) break;
console.log(n);
}
6. 関数(宣言・式・アロー関数、デフォルト引数、可変長引数、再帰)
関数宣言は巻き上げられ定義前でも呼び出せます。アロー関数はthisを持たず簡潔に書けます。デフォルト引数、rest parameters(可変長引数)、再帰呼び出しも基本情報試験の擬似言語問題で頻出のパターンです。
function add(a, b) { // 関数宣言(巻き上げされる)
return a + b;
}
const sub = function (a, b) { // 関数式
return a - b;
};
const mul = (a, b) => a * b; // アロー関数(thisを持たない)
function greet(name = "ゲスト") { // デフォルト引数
return `こんにちは、${name}さん`;
}
function sum(...nums) { // 可変長引数(rest parameters)
return nums.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function factorial(n) { // 再帰
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
7. クロージャ
クロージャとは、内側の関数が外側の関数のスコープ変数を、外側の関数の実行が終わったあとも保持し続けて利用できる仕組みです。外部から直接触れないプライベートな状態を作るのに使われます。
function createCounter() {
let count = 0; // この変数は外から直接アクセスできない
return {
increment() { return ++count; },
reset() { count = 0; },
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
counter.reset();
console.log(counter.increment()); // 1
// count変数はクロージャ内に閉じ込められ、increment/reset経由でのみ操作できる
8. 高階関数(map / filter / reduce)
関数を引数に取ったり関数を返したりする関数を高階関数と呼びます。配列のmap・filter・reduce・find・some・everyは代表的な高階関数です。
const numbers = [1, 2, 3, 4, 5, 6];
const doubled = numbers.map(n => n * 2); // [2,4,6,8,10,12]
const evens = numbers.filter(n => n % 2 === 0); // [2,4,6]
const total = numbers.reduce((sum, n) => sum + n, 0); // 21
const found = numbers.find(n => n > 4); // 5
const hasNegative = numbers.some(n => n < 0); // false
const allPositive = numbers.every(n => n > 0); // true
function applyTwice(fn, value) { // 関数を引数に取る高階関数
return fn(fn(value));
}
console.log(applyTwice(n => n * 2, 3)); // 12
9. オブジェクト・配列操作、分割代入、スプレッド構文
スプレッド構文(...)は配列・オブジェクトの複製や結合に、分割代入は配列・オブジェクトから値を取り出して変数に代入するのに使います。
const original = [1, 2, 3];
const copy = [...original, 4, 5]; // スプレッド構文で配列を複製・結合
const baseUser = { name: "太郎", age: 20 };
const updatedUser = { ...baseUser, age: 21 }; // オブジェクトのコピー&上書き
const { name, age, job = "未設定" } = updatedUser; // 分割代入(デフォルト値も可)
const [first, second, ...rest] = original; // 配列の分割代入とrest
function printPoint({ x, y }) { // 引数の分割代入
console.log(`(${x}, ${y})`);
}
printPoint({ x: 1, y: 2 });
console.log(Object.keys(updatedUser)); // ["name", "age"]
console.log(Object.values(updatedUser)); // ["太郎", 21]
console.log(Object.entries(updatedUser)); // [["name","太郎"],["age",21]]
10. プロトタイプベースOOPとclass構文(継承)
JavaScriptのオブジェクトはプロトタイプチェーンによって機能を共有します。class構文はこの仕組みを扱いやすくした糖衣構文で、extendsによる継承、superによる親クラス呼び出し、プライベートフィールド(#)、getter、staticメンバーが使えます。
// プロトタイプベース(classの内部の仕組み)
function AnimalProto(name) {
this.name = name;
}
AnimalProto.prototype.speak = function () {
return `${this.name}が鳴く`;
};
// class構文(プロトタイプベースの糖衣構文)
class Animal {
#sound; // プライベートフィールド
static count = 0; // 静的プロパティ
constructor(name, sound = "...") {
this.name = name;
this.#sound = sound;
Animal.count++;
}
speak() {
return `${this.name}: ${this.#sound}`;
}
get info() { return `名前: ${this.name}`; } // getter
}
class Dog extends Animal { // 継承
constructor(name) {
super(name, "ワン!"); // 親クラスのコンストラクタ呼び出し
}
speak() { // オーバーライド(ポリモーフィズム)
return `${super.speak()}(犬)`;
}
}
const dog = new Dog("ポチ");
console.log(dog.speak()); // "ポチ: ワン!(犬)"
console.log(dog instanceof Animal); // true
console.log(Animal.count); // 1
11. 例外処理(try / catch / finally、カスタムエラー)
throwで例外を投げ、try/catch/finallyで捕捉します。Errorクラスを継承することで独自の意味を持つカスタムエラーが作れます。
class ValidationError extends Error { // カスタムエラー
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function checkAge(age) {
if (age < 0) {
throw new ValidationError("年齢は0以上である必要があります");
}
return age;
}
try {
checkAge(-1);
} catch (err) {
if (err instanceof ValidationError) {
console.log(`入力エラー: ${err.message}`);
} else {
throw err; // 想定外のエラーは再スロー
}
} finally {
console.log("チェック処理を終了します"); // 成功・失敗にかかわらず必ず実行
}
12. データ構造の実装(スタック・キュー)
スタックは後入れ先出し(LIFO)、キューは先入れ先出し(FIFO)のデータ構造です。JavaScriptでは配列とクラスを使って簡単に実装できます。
class Stack { // LIFO: 後入れ先出し
#items = [];
push(item) { this.#items.push(item); }
pop() { return this.#items.pop(); }
peek() { return this.#items.at(-1); }
get isEmpty() { return this.#items.length === 0; }
}
class Queue { // FIFO: 先入れ先出し
#items = [];
enqueue(item) { this.#items.push(item); }
dequeue() { return this.#items.shift(); } // 先頭を取り出す(O(n))
get isEmpty() { return this.#items.length === 0; }
}
const stack = new Stack();
stack.push(1); stack.push(2); stack.push(3);
console.log(stack.pop()); // 3(最後に入れたものが最初に出る)
const queue = new Queue();
queue.enqueue("A"); queue.enqueue("B");
console.log(queue.dequeue()); // "A"(最初に入れたものが最初に出る)
13. アルゴリズムと計算量(Big-O):二分探索・クイックソート
計算量(Big-O記法)はデータ量の増加に対して処理時間がどう増えるかを表します。線形探索はO(n)ですが、ソート済み配列に対する二分探索はO(log n)と高速です。整列アルゴリズムでは、単純なバブルソートがO(n2)であるのに対し、クイックソートは平均O(n log n)(最悪O(n2))で動作します。
// 二分探索: ソート済み配列が前提。計算量 O(log n)
function binarySearch(sortedArr, target) {
let low = 0, high = sortedArr.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
if (sortedArr[mid] === target) return mid;
if (sortedArr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // 見つからない場合
}
console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3
// クイックソート: 平均計算量 O(n log n)、最悪 O(n^2)
function quickSort(arr) {
if (arr.length <= 1) return arr;
const [pivot, ...others] = arr;
const left = others.filter(n => n < pivot);
const right = others.filter(n => n >= pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(quickSort([5, 2, 8, 1, 9, 3])); // [1,2,3,5,8,9]
// 線形探索はO(n)、二分探索はO(log n)。
// データ量が増えるほど二分探索の優位性が大きくなる(ただし事前ソートが必要)
14. 非同期処理とイベントループ(コールバック→Promise→async/await)
非同期処理は「コールバック関数」→「Promise」→「async/await」の順で書き方が進化してきました。JavaScriptはシングルスレッドですが、イベントループがコールスタック・マイクロタスクキュー(Promise系)・マクロタスクキュー(setTimeout系)を管理し、マイクロタスクの方が優先的に処理されます。
// ①コールバック方式(ネストが深くなりがち=コールバック地獄)
function loadDataCallback(callback) {
setTimeout(() => callback(null, "データ"), 100);
}
// ②Promise方式
function loadDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("データ"), 100);
});
}
// ③async/await方式(Promiseを同期的な見た目で書ける)
async function main() {
try {
const data = await loadDataPromise();
console.log(data);
} catch (err) {
console.error(err);
}
}
main();
// イベントループの実行順序
console.log("1: 同期処理");
setTimeout(() => console.log("4: マクロタスク(setTimeout)"), 0);
Promise.resolve().then(() => console.log("3: マイクロタスク(Promise.then)"));
console.log("2: 同期処理");
// 実行順: 1 → 2 → 3 → 4
// マイクロタスク(Promise等)はマクロタスク(setTimeout等)より先に処理される
15. DOM操作の基礎、イベント処理
ブラウザ上ではdocument.querySelectorなどで要素を取得し、addEventListenerでイベントを登録します。子要素すべてに個別登録する代わりに親要素にまとめて登録する「イベント委譲」もよく使われます。
const list = document.querySelector("#todo-list"); // 要素の取得
const li = document.createElement("li"); // 要素の作成
li.textContent = "新しいタスク";
list.appendChild(li); // 要素の追加
document.querySelector("#add-btn").addEventListener("click", (event) => {
event.preventDefault(); // デフォルト動作の抑制(フォーム送信など)
console.log("追加ボタンが押されました");
});
list.addEventListener("click", (event) => {
if (event.target.tagName === "LI") {
event.target.remove(); // イベント委譲:親要素で子要素のイベントをまとめて処理
}
});
16. モジュール(import / export)
ES Modulesではexportで公開しimportで取り込みます。名前付きエクスポートは複数、デフォルトエクスポートは1ファイルにつき1つだけ使えます。
// math.js(名前付きエクスポート)
export function add(a, b) { return a + b; }
export const PI = 3.14159;
// main.js
import { add, PI } from "./math.js";
console.log(add(1, 2), PI);
// calculator.js(デフォルトエクスポート:1ファイル1つまで)
export default class Calculator { /* ... */ }
// main.js
import Calculator from "./calculator.js";
処理速度改善のポイント
ブラウザで動くJavaScriptは、DOM操作の負荷・アルゴリズムの計算量・メモリ管理の3点を意識すると体感速度が大きく変わります。
- DOM操作はまとめて行う:ループ内で毎回DOMを書き換えるとreflow/repaintが多発します。DocumentFragmentなどで一括更新しましょう。
- ループ処理は用途で使い分ける:可読性重視ならmap/filter、超大量データの単純反復ならforの方が高速な場合があります。
- イベントのデバウンス・スロットリング:scrollやresizeイベントは発火頻度が高いため、間引いて処理を実行します。
- 適切なデータ構造を選ぶ:頻繁な探索・重複排除にはO(1)に近い
Map/Set、配列のindexOfはO(n)であることを意識します。 - 計算量を意識したアルゴリズム選択:データ量が多い場面ではO(n2)のバブルソートよりO(n log n)のクイックソート/マージソートを選びます。
- メモ化(キャッシュ):同じ引数での計算結果を保存し、再計算を避けます。
- 重い処理はWeb Workerへ:メインスレッドをブロックせず、UIの反応性を保てます。
- 不要なグローバル変数・クロージャの多用を避ける:メモリ使用量を抑えGCの負荷を軽減します。