JavaScriptは、ウェブ開発の基盤を形成するスクリプト言語です。その汎用性と柔軟性により、クライアントサイドでのユーザーインタラクションから、サーバーサイドでの処理まで、さまざまな用途で使用されます。以下では、JavaScriptの基礎から応用までを網羅する問題集を紹介し、最後に解答と解説を提供します。
1. 基礎問題
問題1: 変数とデータ型
JavaScriptにはさまざまなデータ型があります。var
, let
, const
の違いを理解し、それぞれを使って以下の操作を行ってください。
設問:
- 変数
x
に数値10
を代入し、それを5
増加させてください。 - 変数
y
に文字列"Hello"
を代入し、その後に" World!"
を追加してください。 const
を使って、定数PI
に円周率を代入し、その値を使って円の面積を計算する関数を作成してください。
制約:
- 関数の再代入は避けてください。
- 定数の再代入はエラーになることを確認してください。
問題2: 関数とスコープ
JavaScriptの関数は、スコープ(変数の有効範囲)を持っています。これを理解し、以下の操作を行ってください。
設問:
- グローバル変数とローカル変数を使い、変数のスコープを確認する関数を作成してください。
- 関数内で定義された変数が、関数外でどのように動作するかをテストしてください。
let
とvar
のスコープの違いを説明し、それを証明するコードを書いてください。
問題3: 配列操作
配列はデータを整理し、管理するために使われます。JavaScriptの配列を使って以下の操作を行ってください。
設問:
- 配列
numbers
に[1, 2, 3, 4, 5]
を代入し、配列の長さを取得してください。 - 配列の各要素を2倍にする関数を作成してください。
- 新しい値
6
を配列の末尾に追加し、最初の要素を削除してください。 - 配列を逆順に並べ替え、新しい配列として返してください。
問題4: オブジェクトの操作
オブジェクトはキーと値のペアを保持するために使用されます。JavaScriptのオブジェクトを使って以下の操作を行ってください。
設問:
- オブジェクト
person
に{name: "John", age: 30, job: "Developer"}
を代入し、そのプロパティを取得してください。 person
オブジェクトに新しいプロパティcity
を追加し、その値を"New York"
に設定してください。age
プロパティの値を31
に更新し、全てのプロパティをループして表示してください。- オブジェクトをコピーし、新しいオブジェクトに別のプロパティを追加してください。
2. 中級問題
問題5: 高階関数とコールバック
高階関数は、他の関数を引数として受け取ったり、返り値として返す関数です。これを理解し、以下の操作を行ってください。
設問:
- コールバック関数を引数として受け取る高階関数を作成し、与えられた配列の要素を操作するプログラムを作成してください。
- コールバック関数を使って、配列の要素が偶数かどうかをチェックし、新しい配列として返してください。
- 配列
[1, 2, 3, 4, 5, 6]
を用いて上記の操作を実行してください。
問題6: プロミスと非同期処理
JavaScriptでは、非同期処理を扱うためにPromise
が使用されます。これを理解し、以下の操作を行ってください。
設問:
Promise
を使って、データの取得をシミュレートする関数を作成してください。- 成功時と失敗時の処理を
then
とcatch
を使って記述してください。 async
とawait
を使った非同期関数を作成し、同様の処理を行ってください。
問題7: クラスとオブジェクト指向
JavaScriptではクラスを使ってオブジェクト指向プログラミングを実現できます。これを理解し、以下の操作を行ってください。
設問:
Person
クラスを作成し、name
とage
のプロパティを持たせてください。greet
メソッドを作成し、"Hello, my name is [name] and I am [age] years old."
と出力するようにしてください。Developer
クラスをPerson
クラスから継承し、job
プロパティを追加してください。Developer
クラスにcode
メソッドを追加し、"[name] is coding."
と出力するようにしてください。
問題8: エラーハンドリング
JavaScriptでは、エラーをキャッチし、適切に処理することが重要です。これを理解し、以下の操作を行ってください。
設問:
try
とcatch
を使って、数値を文字列に変換する関数を作成し、エラーが発生した場合にエラーメッセージを出力してください。- 関数に不正な入力が与えられた場合、例外をスローしてエラーをキャッチしてください。
finally
ブロックを使って、エラーハンドリング後に必ず実行される処理を追加してください。
3. 応用問題
問題9: JavaScriptのパフォーマンス最適化
パフォーマンスの最適化は、効率的なコードを記述する上で重要です。以下のコードを最適化し、パフォーマンスを向上させる方法を提案してください。
設問:
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
- 上記コードを最適化してください。
- 配列の大きさが大きくなった場合に、どのようにパフォーマンスが影響されるかを説明してください。
map
,filter
,reduce
を使ったパフォーマンス向上の例を挙げてください。
問題10: 実際のウェブアプリケーションの開発
実際のアプリケーション開発では、さまざまな機能を統合する必要があります。以下の設問を解決し、簡単なウェブアプリケーションを作成してください。
設問:
- ユーザーが名前と年齢を入力できるフォームを作成し、その情報を画面に表示するウェブアプリケーションを作成してください。
- 入力された名前が空白である場合や、年齢が数字以外の場合にエラーメッセージを表示してください。
- 入力が正しい場合、ユーザーの名前と年齢をオブジェクトとして保存し、それを配列に追加してください。
- 配列内のオブジェクトをループして、すべてのユーザー情報を一覧表示する機能を追加してください。
制約:
- HTMLとCSSを使ってフォームと出力エリアをデザインしてください。
- JavaScriptを使ってフォームのバリデーションとデータの保存を行ってください。
- ユーザー情報は、ページを再読み込みしても消えないように
localStorage
を使って保存してください。
問題11: リアルタイムデータ処理
リアルタイムデータ処理は、最新の情報を常にユーザーに提供するために必要です。以下の設問を解決し、リアルタイムでデータを処理する機能を実装してください。
設問:
- WebSocketを使って、リアルタイムでチャットメッセージを送受信する機能を作成してください。
- ユーザーがメッセージを入力し、送信ボタンをクリックすると、メッセージが全ユーザーに即座に表示されるようにしてください。
- 各メッセージには、送信者の名前と送信時間を含めて表示してください。
- メッセージが送信された際に、サーバー側でログに記録する機能を追加してください。
制約:
- サーバーサイドはNode.jsを使って実装してください。
- クライアントサイドは、HTML, CSS, JavaScriptで実装してください。
- エラーハンドリングを行い、接続が切れた場合の再接続機能を実装してください。
問題12: セキュリティとバグ修正
セキュリティは、ウェブアプリケーション開発において非常に重要です。以下の設問を解決し、アプリケーションのセキュリティを向上させ、バグを修正してください。
設問:
- ユーザーが入力したデータを表示するウェブページを作成してください。ただし、XSS(クロスサイトスクリプティング)攻撃を防ぐために、入力されたデータをエスケープ処理してください。
- SQLインジェクションを防ぐため、サーバーサイドでSQLクエリを実行する際に、パラメータ化されたクエリを使用してください。
- CSRF(クロスサイトリクエストフォージェリ)攻撃を防ぐために、セキュリティトークンを使ったフォーム送信の検証を実装してください。
- バグが発生した際に、ユーザーに適切なエラーメッセージを表示し、開発者には詳細なエラーログを残すようにしてください。
制約:
- サーバーサイドはNode.jsやExpressを使って実装してください。
- セキュリティトークンは、ユーザーがログインした際に生成し、セッションに保存してください。
- フォーム送信時には、セキュリティトークンを送信し、サーバー側でそのトークンが有効かどうかを確認してください。
4. 解答と解説
基礎問題
問題1の解答:
let x = 10;
x += 5;
console.log(x); // 15
let y = "Hello";
y += " World!";
console.log(y); // Hello World!
const PI = 3.14159;
function calculateArea(radius) {
return PI * radius * radius;
}
console.log(calculateArea(5)); // 78.53975
解説:
let
は再代入が可能な変数を宣言する際に使用します。一方、const
は再代入が不可能な定数を宣言する際に使用します。x
に数値10
を代入し、x += 5
でその値を5
増加させます。y
には文字列"Hello"
を代入し、y += " World!"
で文字列を結合しています。const PI
を使って円の面積を計算する関数calculateArea
を作成しています。
問題2の解答:
let globalVar = "I am global";
function checkScope() {
let localVar = "I am local";
console.log(globalVar); // I am global
console.log(localVar); // I am local
}
checkScope();
console.log(globalVar); // I am global
// console.log(localVar); // Uncaught ReferenceError: localVar is not defined
function scopeTest() {
if (true) {
var varTest = "var scope";
let letTest = "let scope";
}
console.log(varTest); // var scope
// console.log(letTest); // Uncaught ReferenceError: letTest is not defined
}
scopeTest();
解説:
globalVar
はグローバルスコープで宣言され、どこからでもアクセス可能です。一方、localVar
は関数checkScope
のローカルスコープで宣言されており、その関数内でしかアクセスできません。var
は関数スコープを持ち、ブロックスコープを無視しますが、let
はブロックスコープを持ちます。そのため、scopeTest
内のletTest
はブロック外ではアクセスできません。
問題3の解答:
let numbers = [1, 2, 3, 4, 5];
console.log(numbers.length); // 5
function doubleArray(arr) {
return arr.map(x => x * 2);
}
console.log(doubleArray(numbers)); // [2, 4, 6, 8, 10]
numbers.push(6);
console.log(numbers); // [1, 2, 3, 4, 5, 6]
numbers.shift();
console.log(numbers); // [2, 3, 4, 5, 6]
let reversedNumbers = numbers.reverse();
console.log(reversedNumbers); // [6, 5, 4, 3, 2]
解説:
- 配列
numbers
の長さはlength
プロパティを使って取得します。 map
メソッドを使って、配列の各要素を2倍にする新しい配列を作成しています。push
メソッドで新しい要素6
を末尾に追加し、shift
メソッドで最初の要素を削除しています。reverse
メソッドを使って配列の順序を逆にしています。
問題4の解答:
let person = {name: "John", age: 30, job: "Developer"};
console.log(person.name); // John
console.log(person.age); // 30
console.log(person.job); // Developer
person.city = "New York";
console.log(person.city); // New York
person.age = 31;
for (let key in person) {
console.log(key + ": " + person[key]);
// name: John
// age: 31
// job: Developer
// city: New York
}
let newPerson = Object.assign({}, person);
newPerson.country = "USA";
console.log(newPerson); // {name: "John", age: 31, job: "Developer", city: "New York", country: "USA"}
解説:
person
オブジェクトのプロパティはドット記法でアクセスできます。- 新しいプロパティを追加するには、ドット記法またはブラケット記法を使います。
for...in
ループを使って、オブジェクトのすべてのプロパティを列挙しています。Object.assign
メソッドを使って、オブジェクトのコピーを作成し、新しいプロパティを追加しています。
中級問題
問題5の解答:
function processArray(arr, callback) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let squares = processArray(numbers, function(x) {
return x * x;
});
console.log(squares); // [1, 4, 9, 16, 25]
let doubles = processArray(numbers, function(x) {
return x * 2;
});
console.log(doubles); // [2, 4, 6, 8, 10]
解説:
processArray
関数は、配列arr
の各要素に対してcallback
関数を適用し、その結果を新しい配列result
に格納します。- コールバック関数を使うことで、
processArray
は汎用的な処理を行うことができ、引数に渡す関数によって動作が変わります。 - この例では、配列内の各要素を平方および2倍にするために、2つの異なるコールバック関数を渡しています。
問題6の解答:
let students = [
{ name: "Alice", score: 85 },
{ name: "Bob", score: 92 },
{ name: "Charlie", score: 87 },
{ name: "David", score: 95 },
{ name: "Eve", score: 79 }
];
students.sort((a, b) => b.score - a.score);
console.log(students);
// [
// { name: 'David', score: 95 },
// { name: 'Bob', score: 92 },
// { name: 'Charlie', score: 87 },
// { name: 'Alice', score: 85 },
// { name: 'Eve', score: 79 }
// ]
let topStudents = students.slice(0, 3);
console.log(topStudents);
// [
// { name: 'David', score: 95 },
// { name: 'Bob', score: 92 },
// { name: 'Charlie', score: 87 }
// ]
解説:
students
配列は、各生徒のスコアを含むオブジェクトの配列です。sort
メソッドを使って、スコアの降順で生徒を並べ替えています。これにより、最高得点の生徒が配列の最初に来るようになります。slice
メソッドを使って、上位3人の生徒を取り出しています。
問題7の解答:
function debounce(func, delay) {
let timeoutId;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
window.addEventListener('resize', debounce(function() {
console.log('Window resized');
}, 500));
解説:
debounce
関数は、指定された時間(delay
)が経過するまで、関数func
の実行を遅延させます。同じイベントが短時間に何度も発生する場合、最後のイベントのみが処理されるようになります。- この例では、
resize
イベントが発生した際に、500msの遅延を設けてからコンソールにメッセージを表示しています。これにより、ウィンドウのサイズ変更イベントが頻繁に発生しても、無駄な処理が防止されます。
問題8の解答:
let originalArray = [1, 2, 3, 4, 5];
function arrayToString(arr) {
return arr.join(", ");
}
function stringToArray(str) {
return str.split(", ").map(Number);
}
let arrayString = arrayToString(originalArray);
console.log(arrayString); // "1, 2, 3, 4, 5"
let newArray = stringToArray(arrayString);
console.log(newArray); // [1, 2, 3, 4, 5]
console.log(newArray === originalArray); // false
console.log(JSON.stringify(newArray) === JSON.stringify(originalArray)); // true
解説:
arrayToString
関数は、配列を文字列に変換します。join
メソッドを使って、配列の各要素を指定した区切り文字で連結します。stringToArray
関数は、文字列を配列に変換します。split
メソッドを使って文字列を分割し、map
メソッドを使って各要素を数値に変換します。- 最後に、新しい配列と元の配列が同一かどうかを確認します。オブジェクトの比較は参照の比較で行われるため、直接比較すると
false
が返されますが、内容が同じであるかどうかはJSON.stringify
を使って確認できます。
問題9の解答:
function isPalindrome(str) {
str = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return str === str.split('').reverse().join('');
}
console.log(isPalindrome("A man, a plan, a canal: Panama")); // true
console.log(isPalindrome("race a car")); // false
console.log(isPalindrome("No 'x' in Nixon")); // true
解説:
isPalindrome
関数は、入力文字列が回文(前から読んでも後ろから読んでも同じ)であるかどうかを判定します。- 入力文字列
str
は、まずtoLowerCase
メソッドで小文字に変換され、正規表現/[^a-z0-9]/g
を使ってアルファベットと数字以外の文字が除去されます。 - その後、文字列を反転させ、元の文字列と比較することで、回文かどうかを判定します。
- この例では、
"A man, a plan, a canal: Panama"
や"No 'x' in Nixon"
が回文であるため、true
を返し、"race a car"
は回文ではないため、false
を返します。
問題10の解答:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ユーザー情報フォーム</title>
</head>
<body>
<h1>ユーザー情報入力</h1>
<form id="userForm">
<label for="name">名前:</label>
<input type="text" id="name" name="name" required>
<label for="age">年齢:</label>
<input type="number" id="age" name="age" required>
<button type="submit">送信</button>
<p id="error"></p>
</form>
<h2>ユーザーリスト</h2>
<ul id="userList"></ul>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('userForm');
const error = document.getElementById('error');
const userList = document.getElementById('userList');
let users = JSON.parse(localStorage.getItem('users')) || [];
function displayUsers() {
userList.innerHTML = '';
users.forEach(user => {
const li = document.createElement('li');
li.textContent = `名前: ${user.name}, 年齢: ${user.age}`;
userList.appendChild(li);
});
}
form.addEventListener('submit', function(event) {
event.preventDefault();
const name = form.name.value.trim();
const age = parseInt(form.age.value.trim());
if (!name) {
error.textContent = '名前を入力してください。';
return;
}
if (isNaN(age) || age <= 0) {
error.textContent = '有効な年齢を入力してください。';
return;
}
const newUser = { name, age };
users.push(newUser);
localStorage.setItem('users', JSON.stringify(users));
displayUsers();
form.reset();
error.textContent = '';
});
displayUsers();
});
</script>
</body>
</html>
解説:
- ユーザー情報を入力するフォームがHTMLで作成されています。名前と年齢を入力し、送信ボタンをクリックすると、入力内容が検証されます。
- 名前が空白だったり、年齢が有効な数字でなかったりする場合は、エラーメッセージが表示されます。
- 入力内容が正しい場合、名前と年齢がオブジェクトとして保存され、配列に追加されます。
localStorage
を使用して、配列に保存されたユーザー情報を保持します。 - ページが読み込まれると、保存されたユーザー情報が自動的に表示されます。
問題11の解答:
サーバー側(Node.js):
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const fs = require('fs');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
app.use(express.static('public'));
io.on('connection', (socket) => {
console.log('新しいユーザーが接続しました。');
socket.on('chat message', (msg) => {
const logMessage = `${msg.sender}: ${msg.text} at ${msg.time}\n`;
fs.appendFile('chat.log', logMessage, (err) => {
if (err) throw err;
});
io.emit('chat message', msg);
});
socket.on('disconnect', () => {
console.log('ユーザーが切断されました。');
});
});
server.listen(3000, () => {
console.log('サーバーがポート3000で起動しました。');
});
クライアント側(HTMLとJavaScript):
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リアルタイムチャット</title>
</head>
<body>
<h1>リアルタイムチャット</h1>
<div id="messages"></div>
<form id="chatForm">
<input id="sender" type="text" placeholder="名前を入力">
<input id="message" type="text" placeholder="メッセージを入力">
<button type="submit">送信</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const form = document.getElementById('chatForm');
const messages = document.getElementById('messages');
form.addEventListener('submit', function(event) {
event.preventDefault();
const sender = document.getElementById('sender').value;
const text = document.getElementById('message').value;
const time = new Date().toLocaleTimeString();
socket.emit('chat message', { sender, text, time });
document.getElementById('message').value = '';
});
socket.on('chat message', function(msg) {
const messageElement = document.createElement('div');
messageElement.textContent = `${msg.time} ${msg.sender}: ${msg.text}`;
messages.appendChild(messageElement);
});
</script>
</body>
</html>
解説:
- このコードは、リアルタイムチャットアプリケーションを構築するためのサーバー側とクライアント側の実装です。
- サーバー側はNode.jsとSocket.IOを使用して、クライアント間のメッセージ送受信を処理します。また、送信されたメッセージはファイル
chat.log
にログとして保存されます。 - クライアント側は、ユーザーが送信したメッセージを表示し、他のユーザーにもリアルタイムで表示されるようにします。
- 接続が切断された場合、Socket.IOは自動的に再接続を試みます。
問題12の解答:
XSS防止のためのエスケープ処理:
function escapeHTML(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const userInput = "<script>alert('XSS');</script>";
const safeInput = escapeHTML(userInput);
document.getElementById('output').innerHTML = safeInput;
SQLインジェクション防止のためのパラメータ化されたクエリ:
const mysql = require('mysql');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'test'
});
const username = 'testUser';
const password = 'testPassword';
connection.query(
'SELECT * FROM users WHERE username = ? AND password = ?',
[username, password],
function (error, results) {
if (error) throw error;
console.log(results);
}
);
CSRF防止のためのトークン検証:
const csrfToken = document.getElementById('csrfToken').value;
document.getElementById('secureForm').addEventListener('submit', function(event) {
event.preventDefault();
const formToken = document.getElementById('csrfToken').value;
if (formToken !== csrfToken) {
alert('不正なトークンです。フォームを再送信してください。');
return;
}
// フォーム送信処理を実行
});
解説:
- XSS(クロスサイトスクリプティング)は、悪意のあるコードを他のユーザーのブラウザで実行させる攻撃です。
escapeHTML
関数を使用して、ユーザー入力から悪意のあるスクリプトを取り除くことができます。 - SQLインジェクションは、ユーザー入力をSQLクエリに直接挿入することで、データベースを不正に操作する攻撃です。パラメータ化されたクエリを使用して、ユーザー入力を安全に処理します。
- CSRF(クロスサイトリクエストフォージェリ)は、ユーザーが意図しないリクエストをサーバーに送信させる攻撃です。CSRFトークンをフォームに含め、そのトークンが一致することを確認することで、CSRF攻撃を防止できます。
このように、JavaScriptの問題を実際に解くことで、セキュリティやパフォーマンス、実用的なコーディングスキルを向上させることができます。問題を解きながら、コードの背後にあるコンセプトを理解し、次に同様の問題に直面した際に自信を持って解決できるようになるでしょう。