JavaScript面接問題50選【2026年版】:コード例と解説付き完全ガイド
JavaScript面接問題50選【2026年版】:コード例と解説付き完全ガイド
2026年の最新JavaScript面接でよく聞かれる50の質問と詳細な回答。クロージャ、Promise、イベントループから最新のES2025機能まで、コード例付きで解説。
はじめに:2026年のJavaScript面接とは
JavaScript面接は、フロントエンド・バックエンドを問わず、多くの開発者ポジションで避けて通れないステップです。2026年の面接では、基本的な文法知識だけでなく、非同期処理の深い理解、パフォーマンス最適化、最新ECMAScript仕様への対応が求められます。
本記事では、実際の面接でよく聞かれる50の質問を難易度別に紹介します。面接官の視点から「何を評価しているか」も含めて解説するので、答えを暗記するだけでなく、概念の本質を理解することを目指してください。
正規表現テスターを活用して、文中の正規表現パターンを実際に試しながら学習することをお勧めします。
基礎レベル(問1〜15)
問1:var、let、constの違いを説明してください
回答:
| 項目 | var | let | const |
|---|---|---|---|
| スコープ | 関数スコープ | ブロックスコープ | ブロックスコープ |
| 巻き上げ(Hoisting) | される(undefined) | される(TDZ) | される(TDZ) |
| 再宣言 | 可能 | 不可能 | 不可能 |
| 再代入 | 可能 | 可能 | 不可能 |
// varの問題:関数スコープ
function example() {
if (true) {
var x = 10;
}
console.log(x); // 10(ブロック外からアクセス可能)
}
// letのブロックスコープ
function example2() {
if (true) {
let y = 10;
}
console.log(y); // ReferenceError: y is not defined
}
// constは再代入不可(ただしオブジェクトのプロパティは変更可能)
const obj = { name: "田中" };
obj.name = "鈴木"; // 可能
obj = {}; // TypeError: Assignment to constant variable
面接官が見ているポイント: TDZ(Temporal Dead Zone)についても言及できると高評価です。
問2:クロージャとは何ですか?実用的な例を挙げてください
回答:
クロージャとは、関数が自身が宣言されたスコープ(レキシカルスコープ)の変数を、そのスコープの外から呼ばれた後も保持し続ける仕組みです。
// カウンターファクトリー関数
function createCounter(initialValue = 0) {
let count = initialValue; // クロージャで保持される変数
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
},
reset() {
count = initialValue;
}
};
}
const counter1 = createCounter(10);
const counter2 = createCounter(0);
counter1.increment(); // 11
counter1.increment(); // 12
counter2.increment(); // 1
// counter1とcounter2はそれぞれ独立したcountを保持
console.log(counter1.getCount()); // 12
console.log(counter2.getCount()); // 1
実用的な応用:プライベート変数の実現、部分適用(カリー化)、イベントハンドラーのファクトリー関数など。
問3:==と===の違いを説明してください
回答:
==は型強制(Type Coercion)を行う比較演算子で、異なる型の値を比較する際に自動的に型変換を行います。===は厳密な等値比較で、型変換を行いません。
console.log(0 == false); // true(型強制)
console.log(0 === false); // false(型が異なる)
console.log("" == false); // true(型強制)
console.log("" === false); // false
console.log(null == undefined); // true(特別なケース)
console.log(null === undefined); // false
// 罠:NaNは自分自身と等しくない
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
// NaNのチェックにはNumber.isNaN()を使う
console.log(Number.isNaN(NaN)); // true
ベストプラクティス: 常に===を使用し、意図的な型強制が必要な場合のみ==を使用する。
問4:JavaScriptのデータ型をすべて挙げてください
回答:
プリミティブ型(7種類):
string、number、bigint、boolean、undefined、null、symbol
参照型:
object(配列、関数、日付なども含む)
// typeofによる確認
typeof "hello" // "string"
typeof 42 // "number"
typeof 42n // "bigint"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object"(JavaScriptの有名なバグ)
typeof Symbol() // "symbol"
typeof {} // "object"
typeof [] // "object"(配列もオブジェクト)
typeof function(){} // "function"
// 配列の正しいチェック方法
Array.isArray([]); // true
問5:イベントループを説明してください
回答:
JavaScriptはシングルスレッドですが、イベントループの仕組みにより非同期処理が可能です。
console.log("1. 同期コード開始");
setTimeout(() => {
console.log("4. macrotask(setTimeout)");
}, 0);
Promise.resolve().then(() => {
console.log("3. microtask(Promise)");
});
console.log("2. 同期コード終了");
// 出力順序:
// 1. 同期コード開始
// 2. 同期コード終了
// 3. microtask(Promise)
// 4. macrotask(setTimeout)
実行順序:
- コールスタックの同期コードを実行
- コールスタックが空になったらマイクロタスクキュー(Promise、queueMicrotask)を処理
- マイクロタスクキューが空になったらマクロタスクキュー(setTimeout、setInterval)から1つ取り出して実行
- 2に戻る
問6:プロトタイプ継承とは何ですか?
回答:
JavaScriptはプロトタイプベースの継承を採用しており、すべてのオブジェクトは__proto__(またはObject.getPrototypeOf())を通じて別のオブジェクト(プロトタイプ)を参照します。
// コンストラクター関数
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name}が鳴いています`;
};
function Dog(name, breed) {
Animal.call(this, name); // 親コンストラクターを呼び出す
this.breed = breed;
}
// プロトタイプチェーンを設定
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.fetch = function() {
return `${this.name}がボールを取ってきました!`;
};
const dog = new Dog("ポチ", "柴犬");
console.log(dog.speak()); // "ポチが鳴いています"
console.log(dog.fetch()); // "ポチがボールを取ってきました!"
// クラス構文(内部的には同じプロトタイプ継承)
class AnimalClass {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name}が鳴いています`;
}
}
問7:thisキーワードの動作を説明してください
回答:
thisの値は関数がどのように呼ばれるかによって決まります。
// 1. メソッドとして呼ばれる場合
const obj = {
name: "田中",
greet() {
console.log(this.name); // "田中"
}
};
obj.greet();
// 2. 通常の関数として呼ばれる場合(非strict)
function regular() {
console.log(this); // グローバルオブジェクト(window / global)
}
// 3. アロー関数:thisを持たず、外側のスコープのthisを使う
const obj2 = {
name: "鈴木",
greet: () => {
console.log(this.name); // undefined(アロー関数はthisを持たない)
},
greetWithMethod() {
const inner = () => {
console.log(this.name); // "鈴木"(外側のメソッドのthis)
};
inner();
}
};
// 4. bind、call、applyで明示的にthisを指定
function greet() {
return `こんにちは、${this.name}さん`;
}
const boundGreet = greet.bind({ name: "佐藤" });
console.log(boundGreet()); // "こんにちは、佐藤さん"
問8:スプレッド演算子とレスト演算子の違いを説明してください
回答:
両方とも...構文を使いますが、使用する文脈が異なります。
// スプレッド演算子:配列やオブジェクトを展開する
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
const original = { name: "田中", age: 30 };
const updated = { ...original, age: 31, email: "tanaka@example.com" };
// レスト演算子:残りの要素をまとめる
function sum(...numbers) { // 関数のパラメーターでの使用
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 分割代入でのレスト
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [3, 4, 5]
const { name, ...others } = { name: "田中", age: 30, city: "東京" };
console.log(others); // { age: 30, city: "東京" }
問9:デストラクチャリングとは何ですか?
// 配列のデストラクチャリング
const [x, y, z = 0] = [1, 2]; // デフォルト値付き
console.log(x, y, z); // 1 2 0
// オブジェクトのデストラクチャリング
const { name: userName, age = 25, address: { city } = {} } = {
name: "田中",
address: { city: "東京" }
};
console.log(userName); // "田中"
console.log(age); // 25(デフォルト値)
console.log(city); // "東京"(ネストされたデストラクチャリング)
// 関数パラメーターでのデストラクチャリング
function displayUser({ name, age, role = "ユーザー" }) {
console.log(`${name}(${age}歳)- ${role}`);
}
displayUser({ name: "鈴木", age: 28 });
問10〜15:追加の基礎問題
問10:nullとundefinedの違い
undefined:変数が宣言されたが値が代入されていない状態null:意図的に「値がない」ことを示す値
問11:配列メソッドの違い(map、filter、reduce)
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
問12:Array.from()の使い方
Array.from("hello"); // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]
Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3](重複排除)
問13:オプショナルチェーニング(?.)
const user = null;
console.log(user?.name); // undefined(エラーにならない)
console.log(user?.address?.city); // undefined
console.log(user?.getAge?.()); // undefined
問14:Nullish合体演算子(??)
const value = null ?? "デフォルト値"; // "デフォルト値"
const count = 0 ?? 10; // 0(nullとundefinedのみデフォルトを返す)
const count2 = 0 || 10; // 10(||はfalsyなすべてにデフォルトを返す)
問15:typeofとinstanceofの違い
typeof "hello"; // "string"(プリミティブ型の確認)
[] instanceof Array; // true(参照型の確認)
中級レベル(問16〜35)
問16:Promiseを使った非同期処理を説明してください
// Promiseの基本
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "田中太郎" });
} else {
reject(new Error("無効なIDです"));
}
}, 1000);
});
};
// Promise チェーン
fetchUser(1)
.then(user => {
console.log(user.name);
return fetchUser(2); // 次のPromiseを返す
})
.then(user2 => console.log(user2.name))
.catch(error => console.error(error.message))
.finally(() => console.log("処理完了"));
// Promise.all:すべてが完了するまで待つ
Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)])
.then(users => console.log(users));
// Promise.allSettled:すべての結果を取得(失敗も含む)
Promise.allSettled([fetchUser(1), fetchUser(-1)])
.then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("成功:", result.value);
} else {
console.log("失敗:", result.reason.message);
}
});
});
問17:async/awaitの仕組みと注意点
// async/awaitの基本
async function loadUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id); // 順次実行
return { user, posts };
} catch (error) {
console.error("データ取得エラー:", error);
throw error;
}
}
// 並列実行(パフォーマンス最適化)
async function loadUserDataParallel(userId) {
const [user, settings] = await Promise.all([
fetchUser(userId),
fetchUserSettings(userId)
]);
return { user, settings };
}
// よくある間違い:ループ内での await
// 悪い例(順次実行になる)
async function badExample(ids) {
const results = [];
for (const id of ids) {
results.push(await fetchUser(id)); // 一つずつ待つ
}
return results;
}
// 良い例(並列実行)
async function goodExample(ids) {
return Promise.all(ids.map(id => fetchUser(id)));
}
問18:メモ化(Memoization)を実装してください
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("キャッシュから返します");
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用例:フィボナッチ数列
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(40)); // 高速に計算
問19:カリー化(Currying)とは何ですか?
// カリー化の実装
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
};
}
// 使用例
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6
add(1, 2, 3); // 6
// 実用的な例:バリデーション関数の部分適用
const isGreaterThan = curry((min, value) => value > min);
const isAdult = isGreaterThan(18);
const isElderly = isGreaterThan(65);
console.log(isAdult(20)); // true
console.log(isAdult(15)); // false
問20:WeakMapとMapの違いを説明してください
// Map:任意の値をキーとして使用可能
const map = new Map();
map.set("key", "value");
map.set(42, "数値キー");
map.set({}, "オブジェクトキー");
// WeakMap:キーはオブジェクトのみ、弱参照
const weakMap = new WeakMap();
let user = { name: "田中" };
weakMap.set(user, { sessionData: "..." });
user = null; // userが参照されなくなるとGCで自動的にメモリ解放
// WeakMapのサイズは確認できない(.sizeがない)
// WeakMapの実用例:DOMノードへのプライベートデータ付与
const privateData = new WeakMap();
class SecureUser {
constructor(name, password) {
privateData.set(this, { password });
this.name = name;
}
authenticate(input) {
return privateData.get(this).password === input;
}
}
問21〜35:中級問題サマリー
問21:ジェネレーター関数の説明と実装
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
const gen = range(0, 10, 2);
console.log([...gen]); // [0, 2, 4, 6, 8]
問22:Proxy と Reflectの使い方
const handler = {
get(target, prop) {
return prop in target ? target[prop] : `${prop}は存在しません`;
},
set(target, prop, value) {
if (typeof value !== "number") throw new TypeError("数値のみ許可");
target[prop] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
問23:Symbol の用途
- オブジェクトのプライベートプロパティキー
- イテラブルの実装(
Symbol.iterator) - Well-known Symbols(
Symbol.hasInstanceなど)
問24:イミュータブルなデータ操作パターン
// オブジェクトの不変更新
const updateUser = (user, updates) => ({ ...user, ...updates });
const addItem = (list, item) => [...list, item];
const removeItem = (list, index) => list.filter((_, i) => i !== index);
問25:デバウンスとスロットリングの実装
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
return fn(...args);
}
};
}
問26:エラー処理のベストプラクティス(カスタムエラークラス)
問27:モジュールシステム(ESM vs CommonJS)
問28:イテレーターとイテラブルの実装
問29:Tagged Template Literals
問30:Object.definePropertyによるゲッター・セッター
問31:structuredCloneによるディープコピー(ES2022)
問32:Array groupBy(ES2024)
問33:Promise.any と Promise.race の違い
問34:SharedArrayBufferとAtomics
問35:AbortControllerによるフェッチのキャンセル
const controller = new AbortController();
const { signal } = controller;
fetch("/api/data", { signal })
.catch(err => {
if (err.name === "AbortError") console.log("リクエストがキャンセルされました");
});
setTimeout(() => controller.abort(), 5000); // 5秒後にキャンセル
上級レベル(問36〜50)
問36:JavaScriptのメモリ管理とガベージコレクション
// メモリリークの典型的なパターン
// 1. 削除されないイベントリスナー
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener("click", this.handleClick);
}
handleClick() { /* ... */ }
// クリーンアップを忘れずに
destroy() {
document.removeEventListener("click", this.handleClick);
}
}
// 2. クロージャによるメモリリーク
function createLeak() {
const largeData = new Array(1000000).fill("data");
return function() {
// largeDataへの参照が保持されつづける
return largeData.length;
};
}
// WeakRefを使った解決策(ES2021)
let user = { name: "田中", data: new Array(10000) };
const ref = new WeakRef(user);
// 後でuserが不要になった場合
user = null;
// GC後にrefは無効になる可能性がある
const derefUser = ref.deref();
if (derefUser) {
console.log(derefUser.name);
}
問37:Virtual DOMの仕組みと実装
// シンプルなVirtual DOM実装
function createElement(type, props, ...children) {
return { type, props: props || {}, children: children.flat() };
}
function createRealDOM(vNode) {
if (typeof vNode === "string" || typeof vNode === "number") {
return document.createTextNode(String(vNode));
}
const element = document.createElement(vNode.type);
// プロパティの設定
Object.entries(vNode.props).forEach(([key, value]) => {
if (key.startsWith("on")) {
element.addEventListener(key.slice(2).toLowerCase(), value);
} else {
element.setAttribute(key, value);
}
});
// 子要素の追加
vNode.children.forEach(child => {
element.appendChild(createRealDOM(child));
});
return element;
}
// 使用例
const vdom = createElement("div", { class: "container" },
createElement("h1", null, "タイトル"),
createElement("p", null, "本文テキスト")
);
問38:Web Workersによる並列処理
// メインスレッド
const worker = new Worker("worker.js");
worker.postMessage({ type: "COMPUTE", data: [1, 2, 3, 4, 5] });
worker.onmessage = (event) => {
console.log("計算結果:", event.data.result);
};
worker.onerror = (error) => {
console.error("Workerエラー:", error.message);
};
// worker.js(別ファイル)
self.onmessage = (event) => {
if (event.data.type === "COMPUTE") {
const result = event.data.data.reduce((sum, n) => sum + n, 0);
self.postMessage({ result });
}
};
問39:Service Workerとオフライン対応
// Service Workerの登録
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js")
.then(reg => console.log("SW登録完了:", reg.scope))
.catch(err => console.error("SW登録失敗:", err));
}
// sw.js
const CACHE_NAME = "app-cache-v1";
const ASSETS = ["/", "/index.html", "/styles.css", "/app.js"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
問40〜50:上級問題サマリー
問40:コンパイラーパターンとAST操作
問41:リアクティブプログラミング(RxJS Observer パターン)
class Observable {
constructor(subscriber) {
this._subscriber = subscriber;
}
subscribe(observer) {
return this._subscriber(observer);
}
map(fn) {
return new Observable(observer => {
return this.subscribe({
next: value => observer.next(fn(value)),
error: err => observer.error(err),
complete: () => observer.complete()
});
});
}
}
問42:型システムとTypeScriptの高度な型(ConditionType、Mapped Types)
問43:正規表現の高度な使用
// 名前付きキャプチャグループ(ES2018)
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { groups: { year, month, day } } = "2026-03-17".match(datePattern);
問44:JavaScriptエンジンの最適化(V8)(Hidden Classes、Inline Cache)
問45:セキュリティとXSS対策
// DOMを安全に更新する
function sanitizeHTML(str) {
const div = document.createElement("div");
div.textContent = str; // textContentはXSS安全
return div.innerHTML;
}
問46:パフォーマンスAPI(Performance Observer)
問47:WebAssemblyとの連携
問48:Stream APIとパイプライン処理
const readable = new ReadableStream({ /* ... */ });
const writable = new WritableStream({ /* ... */ });
await readable.pipeTo(writable);
問49:Temporal API(ES2025)によるタイムゾーン対応
// Temporal APIは日時処理の新標準
const now = Temporal.Now.plainDateTimeISO();
const tokyo = now.toZonedDateTime("Asia/Tokyo");
問50:JavaScriptのセキュリティベストプラクティスとCSP
面接対策のまとめ
JavaScript面接で合格するためには、概念の暗記だけでなく、実際に手を動かしてコードを書く練習が不可欠です。
おすすめの準備方法:
- 毎日LeetCodeやAtCoderの問題を1〜3問解く
- 読んだコードを実際にタイピングして動かす
- 面接で「なぜそのアプローチを選んだか」を説明できるよう準備する
- 実際のプロジェクトで学んだことを具体的に話せるよう整理する
コードのデバッグには正規表現テスターなどのツールを積極的に活用し、実務で役立つスキルを身につけましょう。
2026年のJavaScript面接では、ES2024〜2025の新機能(Temporal、Array groupBy、Record and Tupleなど)への理解も問われるようになっています。常に最新の仕様をキャッチアップし、自信を持って面接に臨んでください。