このガイドでは、JavaScriptの基礎から応用まで段階的に学習しながら、最終的にコマンドライン(CLI)で動作する多機能Todoアプリケーションを完成させます。
完成形のアプリケーション機能:
一歩ずつ、着実にJavaScriptの知識を身につけていきましょう。
プログラミングとは、コンピュータに対して「やってほしいこと」を正確に伝える作業です。料理のレシピのように、手順を順番に書いて、コンピュータがその通りに実行できるようにします。
プログラミングを身近な例で理解しましょう。
料理のレシピとプログラムの比較:
どちらも「手順を正確に伝える」という点で同じです。
JavaScriptは、Webサイトを動的にしたり、サーバーでプログラムを動かしたりできるプログラミング言語です。世界中で最も使われている言語の一つで、初心者にも学びやすい特徴があります。
JavaScriptの特徴:
なぜJavaScriptを学ぶのか:
CLIアプリケーションとは、Command Line Interface(コマンドライン インターフェース)の略で、文字だけでやり取りするプログラムです。マウスを使わず、キーボードだけで操作します。
今回作成するTodoアプリの機能:
なぜCLIアプリから始めるのか:
プログラミングには「開発環境」が必要です。これは、プログラムを書いて、実行して、確認するためのツール一式のことです。料理で言えば、包丁・まな板・コンロのような道具です。
必要なツール:
Node.jsは、ブラウザの外でJavaScriptを実行できるようにするソフトウェアです。通常JavaScriptはWebサイトの中でしか動きませんが、Node.jsがあることで、パソコン上で直接JavaScriptプログラムを実行できます。
Node.jsの確認手順:
Windowsの場合: スタートメニューで「cmd」と検索 → 「コマンドプロンプト」をクリック Macの場合: LaunchpadでTerminalを検索 → 「ターミナル」をクリック
黒い画面(コマンドプロンプト/ターミナル)が開いたら、以下を入力してEnterキーを押します:
node --version
v18.17.0
のようなバージョン番号が表示される
失敗例: 'node'は、内部コマンドまたは外部コマンドとして認識されていません
Node.jsがインストールされていない場合:
node --version
で確認VSCode(Visual Studio Code)は、プログラムを書くための高機能なテキストエディタです。Microsoftが開発した無料のソフトで、世界中のプログラマーが使用しています。単なるメモ帳とは違い、プログラミングに特化した便利な機能がたくさん付いています。
VSCodeの便利な機能:
VSCodeのインストール:
プログラミングでは、関連するファイルをまとめて整理することが重要です。今回のプロジェクト専用のフォルダを作成し、そこですべての作業を行います。これにより、ファイルが散らばることを防ぎ、プロジェクト管理がしやすくなります。
フォルダ作成手順:
デスクトップに新しいフォルダを作成
VSCodeでフォルダを開く
新しいファイルを作成
環境が正しくセットアップされているかを確認するため、簡単なプログラムを書いて実行してみます。この確認により、後のステップで問題が発生するリスクを事前に回避できます。
動作確認のためのテストコード:
VSCodeで作成したtodo.js
ファイルに以下を入力してください:
console.log("Hello, JavaScript!");
console.log("Todo アプリケーション開発開始!");
プログラムの実行:
cd Desktop/todo-cli-project
node todo.js
期待される結果:
Hello, JavaScript!
Todo アプリケーション開発開始!
うまくいかない場合のトラブルシューティング:
エラーメッセージ | 原因 | 解決方法 |
---|---|---|
node: command not found |
Node.jsがインストールされていない | ステップ0.5に戻ってNode.jsをインストール |
cannot find module |
ファイルパスが間違っている | pwd (Mac)またはdir (Windows)で現在位置を確認 |
syntax error |
コードに誤りがある | コードをもう一度確認し、正確に入力 |
プログラミングでは、大きな問題を小さな問題に分解して、一つずつ解決していくアプローチが重要です。これを「分割統治法」と呼びます。料理で複雑な料理を作る時も、「下ごしらえ」「調理」「盛り付け」に分けて考えるのと同じです。
今回作成するTodoアプリの分解:
プログラミングの3つの基本構造:
これらの組み合わせで、どんな複雑なプログラムも作ることができます。
✅ 準備完了チェック
すべてにチェックが付いたら、次のセクションに進みましょう!
変数とは、データを保存する「名前付きの箱」です。現実世界でラベルを付けた箱に物を入れて整理するように、プログラムでもデータに名前を付けて管理します。JavaScriptでは、この箱を作るためにlet
、const
、var
というキーワードを使います。
変数を日常生活で例えると:
なぜ変数が必要なのか:
let, const, varの違い:
最初の変数を宣言してみましょう:
___ userName = "田中太郎";
___ userAge = 25;
正解
let userName = "田中太郎";
let userAge = 25;
実際に確認してみよう:
上記のコードをtodo.js
に追加して実行してみてください。変数の値をconsole.log()
で表示できます:
console.log("ユーザー名:", userName);
console.log("年齢:", userAge);
プログラムで扱うデータには種類があります。文字、数値、真偽値(はい・いいえ)など、それぞれ異なる特徴を持ちます。これを「データ型」と呼びます。適切なデータ型を使うことで、バグを防ぎ、効率的なプログラムを作成できます。
主要なデータ型:
文字列(String): 文字や文章
let message = "こんにちは"; // ダブルクォート
let name = 'JavaScript'; // シングルクォート
数値(Number): 数字
let age = 25; // 整数
let price = 99.99; // 小数
真偽値(Boolean): trueまたはfalse
let isCompleted = true; // 完了している
let isVisible = false; // 表示されていない
配列(Array): 複数のデータのリスト
let fruits = ["りんご", "バナナ", "オレンジ"];
オブジェクト(Object): 関連するデータのまとまり
let person = {
name: "田中",
age: 25,
isStudent: true
};
データ型を確認する方法:
let testData = "Hello";
console.log("データ型:", ___ testData);
正解
let testData = "Hello";
console.log("データ型:", typeof testData);
配列は、同じ種類の複数のデータを順序立てて保存するデータ構造です。本棚に本を順番に並べるように、データを0番、1番、2番...という番号(インデックス)で管理します。Todoアプリでは、複数のタスクを配列で管理します。
なぜ配列が必要なのか:
悪い例(配列を使わない場合):
let task1 = "買い物";
let task2 = "掃除";
let task3 = "勉強";
// タスクが100個あったら、task1, task2, ... task100まで書く必要がある
良い例(配列を使う場合):
let tasks = ["買い物", "掃除", "勉強"];
// いくつタスクが増えても配列一つで管理できる
配列の基本操作:
// 空の配列を作成
let tasks = [];
// 要素を追加
tasks.___("新しいタスク");
// 要素の数を確認
console.log("タスク数:", tasks.___);
// 特定の要素にアクセス(最初は0番)
console.log("最初のタスク:", tasks[0]);
正解
// 空の配列を作成
let tasks = [];
// 要素を追加
tasks.push("新しいタスク");
// 要素の数を確認
console.log("タスク数:", tasks.length);
// 特定の要素にアクセス(最初は0番)
console.log("最初のタスク:", tasks[0]);
配列のインデックスの重要な特徴:
undefined
が返されるオブジェクトは、関連する情報をプロパティ(属性)としてまとめて管理するデータ構造です。人物カードに「名前」「年齢」「職業」などの情報をまとめて書くように、プログラムでも関連するデータを一つのオブジェクトにまとめます。
オブジェクトを現実世界で例えると:
人物の情報:
Todoタスクの情報:
オブジェクトの作成方法:
let person = {
name: "田中太郎", // プロパティ名: 値
age: ___, // 数値
isStudent: ___, // 真偽値
hobbies: ["読書", "映画鑑賞"] // 配列
};
正解
let person = {
name: "田中太郎",
age: 25,
isStudent: false,
hobbies: ["読書", "映画鑑賞"]
};
オブジェクトのプロパティにアクセスする方法:
// ドット記法
console.log(person.name); // "田中太郎"
console.log(person.age); // 25
// ブラケット記法
console.log(person["name"]); // "田中太郎"
関数は、特定の処理をまとめて名前を付けたものです。料理のレシピのように、一度書いておけば何度でも使えます。「材料(引数)を渡すと、料理(戻り値)が完成する」という仕組みです。関数を使うことで、同じコードを何度も書く必要がなくなり、保守性が向上します。
関数を料理のレシピで例えると:
カレーのレシピ(関数):
プログラムの関数:
関数の基本構文:
___ addNumbers(a, b) {
let result = a + b;
___ result;
}
// 関数の使用
let sum = addNumbers(5, 3);
console.log(sum); // 8
正解
function addNumbers(a, b) {
let result = a + b;
return result;
}
// 関数の使用
let sum = addNumbers(5, 3);
console.log(sum); // 8
関数を使う利点:
実際にTodoアプリで使用するタスク作成関数を実装しましょう。
___ createTask(title, desc, priority, important) {
return {
title: title || "無題",
desc: desc ?? "説明なし",
priority: Number(priority),
important: Boolean(important),
completed: false,
created: new Date()
};
}
正解
function createTask(title, desc, priority, important) {
return {
title: title || "無題",
desc: desc ?? "説明なし",
priority: Number(priority),
important: Boolean(important),
completed: false,
created: new Date()
};
}
プログラムでは、予期しないデータ(空文字、null、undefined)が渡される可能性があります。論理演算子を使ってデフォルト値を設定することで、エラーを防ぎ、安全なプログラムを作成できます。
論理演算子の種類と使い分け:
|| (OR演算子): 左側がfalsyなら右側を使用
false
, 0
, ""
, null
, undefined
, NaN
?? (Null合体演算子): 左側がnullまたはundefinedなら右側を使用
実際の例で違いを確認:
// || の場合
let title1 = "" || "無題"; // 結果: ___
let title2 = null || "無題"; // 結果: ___
let title3 = "買い物" || "無題"; // 結果: ___
// ?? の場合
let desc1 = "" ?? "説明なし"; // 結果: ___
let desc2 = null ?? "説明なし"; // 結果: ___
let desc3 = "重要な会議" ?? "説明なし"; // 結果: ___
正解
// || の場合
let title1 = "" || "無題"; // 結果: "無題"
let title2 = null || "無題"; // 結果: "無題"
let title3 = "買い物" || "無題"; // 結果: "買い物"
// ?? の場合
let desc1 = "" ?? "説明なし"; // 結果: ""
let desc2 = null ?? "説明なし"; // 結果: "説明なし"
let desc3 = "重要な会議" ?? "説明なし"; // 結果: "重要な会議"
ユーザーからの入力は通常文字列として受け取られますが、プログラム内では適切なデータ型に変換する必要があります。型変換を行うことで、意図しない動作やエラーを防げます。
型変換が必要な理由:
// 問題のある例(型変換なし)
let age1 = "25";
let age2 = "30";
console.log(age1 + age2); // "2530" (文字列として結合される)
// 正しい例(型変換あり)
let age3 = Number("25");
let age4 = Number("30");
console.log(age3 + age4); // 55 (数値として計算される)
型変換関数の使用:
// 文字列を数値に変換
let priority = ___("3");
let importance = ___("true");
console.log("優先度:", priority, "型:", typeof priority);
console.log("重要度:", importance, "型:", typeof importance);
正解
// 文字列を数値に変換
let priority = Number("3");
let importance = Boolean("true");
console.log("優先度:", priority, "型:", typeof priority);
console.log("重要度:", importance, "型:", typeof importance);
型変換の注意点:
Number("abc")
→ NaN
(Not a Number)Boolean("")
→ false
Boolean("false")
→ true
(文字列"false"は真値)✅ Section 1 理解度チェック
次のセクションに進む前に、実際にコードを書いて動作確認をしましょう!
forEachは、配列の各要素に対して指定した関数を順番に実行するメソッドです。forループとは異なり、インデックスの管理が自動化されるため、エラーが発生しにくく、コードが読みやすくなります。
forEachの基本構文:
配列名.forEach(function(要素, インデックス) {
// 各要素に対する処理
});
従来のforループとの比較:
// forループ(手動管理)
for (let i = 0; i < tasks.length; i++) {
console.log(tasks[i].title);
}
// forEach(自動管理)
tasks.forEach(function(task, index) {
console.log(task.title);
});
下のコードを完成させてください:
let fruits = ["りんご", "バナナ", "オレンジ"];
fruits.___(___(fruit, index) {
console.log(`${index}: ${fruit}`);
});
正解
let fruits = ["りんご", "バナナ", "オレンジ"];
fruits.forEach(function(fruit, index) {
console.log(`${index}: ${fruit}`);
});
アロー関数は、function キーワードを使わずに関数を簡潔に書ける構文です。ES6で導入され、特にコールバック関数として広く使用されます。基本的な変換ルールを覚えることで、既存のfunction記法をアロー関数に変換できます。
アロー関数の変換ルール:
function
キーワードを削除=>
を記述変換の実例:
// function記法
function(task, index) {
console.log(task.title);
}
// アロー関数に変換
(task, index) => {
console.log(task.title);
}
下のfunction記法をアロー関数に変換してください:
// function記法
numbers.forEach(function(num) {
console.log(num * 2);
});
// アロー関数版
numbers.forEach(___);
正解
// function記法
numbers.forEach(function(num) {
console.log(num * 2);
});
// アロー関数版
numbers.forEach((num) => {
console.log(num * 2);
});
テンプレートリテラルはES6で導入された文字列処理機能です。従来の文字列連結(+
演算子)と比較して:
${}
内で計算や関数呼び出しが可能実務での主な使用例:
従来の方法との比較:
// 従来の文字列連結
let message = "ユーザー名: " + userName + ", 年齢: " + age;
// テンプレートリテラル
let message = `ユーザー名: ${userName}, 年齢: ${age}`;
下の文字列連結をテンプレートリテラルに変換してください:
let itemNumber = 3;
let itemName = "ペン";
let price = 150;
// 従来の文字列連結
let info = itemNumber + "番: " + itemName + " (" + price + "円)";
// テンプレートリテラル版
let info2 = ___;
正解
let itemNumber = 3;
let itemName = "ペン";
let price = 150;
// 従来の文字列連結
let info = itemNumber + "番: " + itemName + " (" + price + "円)";
// テンプレートリテラル版
let info2 = `${itemNumber}番: ${itemName} (${price}円)`;
三項演算子(? :
)は、条件によって異なる値を返すための簡潔な書き方です。if文を短く書くことができ、テンプレートリテラルの中でよく使用されます。
三項演算子の基本構文:
条件 ? 真の場合の値 : 偽の場合の値
if文との比較:
// if文を使った場合
let mark;
if (task.completed) {
mark = "x";
} else {
mark = " ";
}
// 三項演算子を使った場合
let mark = task.completed ? "x" : " ";
下の穴埋めを完成させてください:
let isCompleted = true;
let status = isCompleted ? ___ : ___;
console.log(status); // "完了" と表示される
正解
let isCompleted = true;
let status = isCompleted ? "完了" : "未完了";
console.log(status); // "完了" と表示される
これまで学習した知識を組み合わせて、より複雑な表示を作成しましょう。
let i = 0;
let task = {
title: "買い物",
completed: false
};
// テンプレートリテラルと三項演算子を組み合わせ
console.log(`${i + 1}: [${task.completed ? ___ : ___}] ${task.title}`);
正解
let i = 0;
let task = {
title: "買い物",
completed: false
};
// テンプレートリテラルと三項演算子を組み合わせ
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
if
文は、条件によって処理を分岐させるための基本的な制御構文です。配列が空の場合の処理など、エラーを防ぐための重要な仕組みです。
if文の基本構文:
if (条件) {
// 条件が真の場合の処理
}
配列の長さをチェックする例:
if (tasks.length === 0) {
console.log("タスクがありません");
}
下の穴埋めを完成させてください:
if (tasks.___ === 0) {
console.log("タスクがありません");
}
正解
if (tasks.length === 0) {
console.log("タスクがありません");
}
return
文は、関数の実行を終了し、呼び出し元に値を返すための構文です。配列が空の場合など、これ以上処理を続ける必要がない時に使用します。
return文の役割:
function checkTasks() {
if (tasks.length === 0) {
console.log("タスクがありません");
return; // ここで関数を終了(以降の処理を実行しない)
}
console.log("タスクがあります"); // 上でreturnされた場合、この行は実行されない
}
下の穴埋めを完成させてください:
function showTaskCount() {
if (tasks.length === 0) {
console.log("タスクがありません");
___; // 関数を終了
}
console.log("タスク数:", tasks.length);
}
正解
function showTaskCount() {
if (tasks.length === 0) {
console.log("タスクがありません");
return; // 関数を終了
}
console.log("タスク数:", tasks.length);
}
これまで学習した4つの要素を組み合わせて、完全なタスク表示関数を作成します。
プログラミングでは、個別に学習した概念を組み合わせて実用的な機能を実装します。forEach、アロー関数、テンプレートリテラル、三項演算子を統合することで、読みやすく効率的なコードが書けます。
統合する要素の確認:
下の関数を完成させてください:
function listTasksForEach() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
// 4つの要素を全て使って実装
tasks.___((task, i) => {
console.log(___);
});
}
表示形式: 1: [x] 買い物
(完了済みはx、未完了は空白)
正解
function listTasksForEach() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
// 4つの要素を全て使って実装
tasks.forEach((task, i) => {
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
});
}
for
ループは、初期値、条件、増減処理を明示的に指定する繰り返し構文です。配列のインデックスを直接制御したい場合に使用します。forEachと同じ結果を得ることができますが、より詳細な制御が可能です。
for ループの基本構文:
for (初期値; 条件; 増減処理) {
// 繰り返したい処理
}
配列を使ったfor ループの例:
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
console.log(task.title);
}
下の穴埋めを完成させてください:
for (let i = ___; i < tasks.___; ___) {
const task = tasks[i];
console.log(`${i + 1}: ${task.title}`);
}
正解
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
console.log(`${i + 1}: ${task.title}`);
}
同じ表示処理をfor
ループで実装しましょう。
function listTasksFor() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
for (let i = ___; i < tasks.___; ___) {
const task = tasks[i];
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
}
}
正解
function listTasksFor() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
}
}
while
ループは、指定した条件がtrueの間、処理を繰り返します。条件が最初からfalseの場合、一度も実行されません。手動でカウンターを管理する必要があります。
while ループの基本構文:
while (条件) {
// 繰り返したい処理
}
配列を使ったwhile ループの例:
let i = 0;
while (i < tasks.length) {
const task = tasks[i];
console.log(task.title);
i++; // カウンターを手動で増加
}
下の穴埋めを完成させてください:
let i = ___;
___ (i < tasks.length) {
const task = tasks[i];
console.log(`${i + 1}: ${task.title}`);
___;
}
正解
let i = 0;
while (i < tasks.length) {
const task = tasks[i];
console.log(`${i + 1}: ${task.title}`);
i++;
}
同じ表示処理をwhile
ループで実装しましょう。
function listTasksWhile() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
let i = ___;
___ (i < tasks.length) {
const task = tasks[i];
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
___;
}
}
正解
function listTasksWhile() {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
let i = 0;
while (i < tasks.length) {
const task = tasks[i];
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
i++;
}
}
includes
は、文字列に特定の文字列が含まれているかを判定するメソッドです。大文字小文字を区別し、完全一致ではなく部分一致で検索します。検索機能の実装において基礎となるメソッドです。
includesの基本例:
let title = "買い物リスト";
// 部分一致の検索
console.log(title.includes("買い")); // true
console.log(title.includes("物")); // true
console.log(title.includes("買い物")); // true
console.log(title.includes("掃除")); // false
下の穴埋めを完成させてください:
let taskTitle = "重要な会議の準備";
console.log(taskTitle.___("会議")); // true が表示される
正解
let taskTitle = "重要な会議の準備";
console.log(taskTitle.includes("会議")); // true が表示される
filterは、配列から条件に合う要素のみを抽出して新しい配列を作成するメソッドです。元の配列は変更されず、指定した条件を満たす要素だけで構成された配列が返されます。データの絞り込みや検索機能の実装において基礎となるメソッドです。
filterの基本構文:
新しい配列 = 元の配列.filter(要素 => 条件式);
数値の絞り込み例:
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
console.log(numbers); // [1, 2, 3, 4, 5] (元の配列は変更されない)
下の条件に合う要素を抽出してください:
let ages = [15, 22, 17, 25, 19];
// 20歳以上の年齢のみを抽出
let adults = ages.___(age => age ___ 20);
console.log(adults); // [22, 25] と表示される
正解
let ages = [15, 22, 17, 25, 19];
// 20歳以上の年齢のみを抽出
let adults = ages.filter(age => age >= 20);
console.log(adults); // [22, 25] と表示される
filterとincludesを組み合わせることで、強力な検索機能を実装できます。
// タスクタイトルに特定の文字列が含まれるタスクを検索
let searchWord = "買い";
let foundTasks = tasks.___(task => task.title.___(searchWord));
下の穴埋めを完成させてください:
let searchWord = "買い";
let foundTasks = tasks.___(task => task.title.___(searchWord));
正解
let searchWord = "買い";
let foundTasks = tasks.filter(task => task.title.includes(searchWord));
検索結果が存在するかどうかを確認し、適切に表示する処理を学習しましょう。
length
プロパティを使用して配列の要素数を確認することで、検索結果が存在するかを判定できます。結果が0の場合は「見つからない」メッセージを表示し、1以上の場合は結果を表示します。
検索機能の完全な実装を作成してみましょう:
function searchTasks(searchWord) {
const foundTasks = tasks.filter(task => task.title.includes(searchWord));
if (foundTasks.___ > 0) {
foundTasks.forEach((task, i) => {
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
});
} else {
console.log("該当するタスクが見つかりません");
}
}
正解
function searchTasks(searchWord) {
const foundTasks = tasks.filter(task => task.title.includes(searchWord));
if (foundTasks.length > 0) {
foundTasks.forEach((task, i) => {
console.log(`${i + 1}: [${task.completed ? "x" : " "}] ${task.title}`);
});
} else {
console.log("該当するタスクが見つかりません");
}
}
否定演算子(!
)は、真偽値を反転させる演算子です。true
をfalse
に、false
をtrue
に変換します。「〜でない」という条件を表現する際に使用します。
否定演算子の基本例:
let isCompleted = true;
let isNotCompleted = !isCompleted; // false
let isEmpty = false;
let isNotEmpty = !isEmpty; // true
タスクでの使用例:
// 完了していないタスク
task.completed // 完了している場合true
!task.completed // 完了していない場合true
下の穴埋めを完成させてください:
let isFinished = false;
let isNotFinished = ___isFinished;
console.log(isNotFinished); // true が表示される
正解
let isFinished = false;
let isNotFinished = !isFinished;
console.log(isNotFinished); // true が表示される
every
は、配列のすべての要素が指定した条件を満たすかどうかを判定するメソッドです。一つでも条件を満たさない要素があればfalse
を返し、すべての要素が条件を満たす場合のみtrue
を返します。「全件チェック」の処理において重要なメソッドです。
everyの基本構文:
配列.every(要素 => 条件)
everyの動作例:
let numbers = [2, 4, 6, 8];
let allEven = numbers.every(num => num % 2 === 0); // すべて偶数?
console.log(allEven); // true
let mixed = [2, 4, 5, 8];
let allEvenMixed = mixed.every(num => num % 2 === 0); // すべて偶数?
console.log(allEvenMixed); // false(5が奇数のため)
下の穴埋めを完成させてください:
let tasks = [
{completed: true},
{completed: true},
{completed: true}
];
let allTasksCompleted = tasks.___(task => task.completed);
console.log("すべて完了:", allTasksCompleted); // true が表示される
正解
let tasks = [
{completed: true},
{completed: true},
{completed: true}
];
let allTasksCompleted = tasks.every(task => task.completed);
console.log("すべて完了:", allTasksCompleted); // true が表示される
some
は、配列の要素の中で少なくとも一つが指定した条件を満たすかどうかを判定するメソッドです。一つでも条件を満たす要素があればtrue
を返し、すべての要素が条件を満たさない場合のみfalse
を返します。「存在チェック」の処理において重要なメソッドです。
someの基本構文:
配列.some(要素 => 条件)
someの動作例:
let numbers = [1, 3, 5, 7];
let hasEven = numbers.some(num => num % 2 === 0); // 偶数が存在する?
console.log(hasEven); // false
let mixed = [1, 3, 4, 7];
let hasEvenMixed = mixed.some(num => num % 2 === 0); // 偶数が存在する?
console.log(hasEvenMixed); // true(4が偶数のため)
下の穴埋めを完成させてください:
let tasks = [
{completed: true},
{completed: false},
{completed: true}
];
// 未完了のタスクが存在するかチェック
let hasIncompleteTask = tasks.___(task => ___task.completed);
console.log("未完了あり:", hasIncompleteTask); // true が表示される
正解
let tasks = [
{completed: true},
{completed: false},
{completed: true}
];
// 未完了のタスクが存在するかチェック
let hasIncompleteTask = tasks.some(task => !task.completed);
console.log("未完了あり:", hasIncompleteTask); // true が表示される
every
とsome
は対になるメソッドです。everyは「全部」、someは「一部」の条件チェックに使用します。否定演算子と組み合わせることで、様々な条件判定が可能になります。
everyとsomeの違い:
every
: すべてが条件を満たすか → 全員が合格しているか?some
: 少なくとも一つが条件を満たすか → 不合格者がいるか?実際の使用例:
// 重要でないタスクを見つける
let notImportantTasks = tasks.filter(task => ___task.important);
正解
// 重要でないタスクを見つける
let notImportantTasks = tasks.filter(task => !task.important);
これまで学習した知識を組み合わせて、完全な統計表示機能を作成しましょう。
必要な知識の確認:
function showStatistics() {
const total = tasks.length;
const completed = tasks.___(task => task.completed).length;
const allCompleted = tasks.___(task => task.completed);
const hasIncomplete = tasks.___(task => ___task.completed);
console.log(`総タスク数: ${total}`);
console.log(`完了済み: ${completed}`);
console.log(`未完了: ${total - completed}`);
console.log(`すべて完了: ${allCompleted ? "はい" : "いいえ"}`);
console.log(`未完了あり: ${hasIncomplete ? "はい" : "いいえ"}`);
}
正解
function showStatistics() {
const total = tasks.length;
const completed = tasks.filter(task => task.completed).length;
const allCompleted = tasks.every(task => task.completed);
const hasIncomplete = tasks.some(task => !task.completed);
console.log(`総タスク数: ${total}`);
console.log(`完了済み: ${completed}`);
console.log(`未完了: ${total - completed}`);
console.log(`すべて完了: ${allCompleted ? "はい" : "いいえ"}`);
console.log(`未完了あり: ${hasIncomplete ? "はい" : "いいえ"}`);
}
mapは、配列の各要素に変換処理を適用して新しい配列を作成するメソッドです。元の配列は変更されず、変換結果として新しい配列が返されます。データの表示形式変更やデータ加工において重要な役割を果たします。
mapの基本構文:
新しい配列 = 元の配列.map(要素 => 変換処理);
シンプルな変換例:
let numbers = [1, 2, 3];
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] (元の配列は変更されない)
下の配列変換を完成させてください:
let prices = [100, 200, 300];
// 全ての価格に税率1.1を掛けた新しい配列を作成
let taxIncluded = prices.___(price => price * ___);
console.log(taxIncluded); // [110, 220, 330] と表示される
正解
let prices = [100, 200, 300];
// 全ての価格に税率1.1を掛けた新しい配列を作成
let taxIncluded = prices.map(price => price * 1.1);
console.log(taxIncluded); // [110, 220, 330] と表示される
JavaScript の配列メソッドは、それぞれ異なる目的を持ちます。適切な場面で適切なメソッドを選択することで、効率的で読みやすいコードを書くことができます。
各メソッドの特徴と使用場面:
メソッド | 用途 | 戻り値 | 使用例 |
---|---|---|---|
forEach |
全要素の処理 | なし | 全タスクの表示 |
map |
要素の変換 | 新しい配列 | 番号付きリスト作成 |
filter |
条件による絞り込み | 新しい配列 | 検索、未完了タスク抽出 |
every |
全要素が条件を満たすか | true/false | 全タスク完了チェック |
some |
一つでも条件を満たすか | true/false | 未完了タスク存在チェック |
実際の使用例:
// すべての要素を処理
tasks.forEach(task => console.log(task.title));
// 要素を変換
let numberedTasks = tasks.map((task, i) => `${i + 1}. ${task.title}`);
// 条件で絞り込み
let incompleteTasks = tasks.filter(task => !task.completed);
// 全部チェック
let allDone = tasks.every(task => task.completed);
// 存在チェック
let hasUrgent = tasks.some(task => task.priority > 3);
✅ Section 2 理解度チェック
次のセクションに進む前に、実際にコードを書いて動作確認をしましょう!
JavaScriptには2種類の等価演算子があります。==
(等価演算子)は型変換を行って比較し、===
(厳密等価演算子)は型変換を行わずに比較します。この違いを理解することは、バグを防ぎ、予期しない動作を避けるために非常に重要です。
なぜ2種類の等価演算子があるのか:
JavaScriptは「動的型付け言語」のため、同じ値でも異なるデータ型で表現される場合があります。
型変換の例:
let numberValue = 5; // 数値の5
let stringValue = "5"; // 文字列の"5"
// 見た目は同じ「5」だが、データ型が異なる
console.log(typeof numberValue); // "number"
console.log(typeof stringValue); // "string"
==
は比較時に自動的に型変換を行います。異なるデータ型同士でも、値が「同じと見なせる」場合はtrueを返します。一見便利ですが、予期しない結果を生むことがあるため注意が必要です。
== による型変換の例:
console.log(5 == "5"); // true(文字列"5"が数値5に変換される)
console.log(true == 1); // true(trueが1に変換される)
console.log(false == 0); // true(falseが0に変換される)
console.log(null == undefined); // true(特殊ルール)
下の結果を予想してください:
console.log(0 == ""); // 結果: ___
console.log(0 == false); // 結果: ___
正解
console.log(0 == ""); // 結果: true
console.log(0 == false); // 結果: true
===
は型変換を一切行わず、値とデータ型の両方が完全に一致する場合のみtrueを返します。より予測可能で安全な比較を行うため、現代のJavaScript開発では===
の使用が強く推奨されています。
=== による厳密な比較の例:
console.log(5 === "5"); // false(数値と文字列は異なる型)
console.log(true === 1); // false(真偽値と数値は異なる型)
console.log(false === 0); // false(真偽値と数値は異なる型)
console.log(null === undefined); // false(異なる型)
下の結果を予想してください:
console.log(5 === 5); // 結果: ___
console.log("hello" === "hello"); // 結果: ___
console.log(true === true); // 結果: ___
正解
console.log(5 === 5); // 結果: true
console.log("hello" === "hello"); // 結果: true
console.log(true === true); // 結果: true
等価演算子と同様に、不等価演算子にも2種類あります。!=
は型変換を行い、!==
は型変換を行いません。「等しくない」ことを判定する際に使用します。
不等価演算子の例:
// != (型変換あり)
console.log(5 != "5"); // false(変換後に等しいため)
console.log(5 != "6"); // true(変換後も等しくないため)
// !== (型変換なし)
console.log(5 !== "5"); // true(型が異なるため)
console.log(5 !== 5); // false(完全に同じため)
下の穴埋めを完成させてください:
let userAge = 25;
let inputAge = "25";
// 値は同じだが型が違うかチェック
if (userAge ___ inputAge) {
console.log("型が異なります");
}
正解
let userAge = 25;
let inputAge = "25";
// 値は同じだが型が違うかチェック
if (userAge !== inputAge) {
console.log("型が異なります");
}
実際のTodoアプリで比較演算子の違いを確認する機能を実装しましょう。
function showDetailedStats() {
const total = tasks.length;
const completed = tasks.filter(task => task.completed).length;
// 等価演算子の比較(型変換あり)
const isEqual = total ___ completed;
// 厳密等価演算子の比較(型変換なし)
const isStrictEqual = total ___ completed;
console.log(`総タスク数: ${total} (型: ${typeof total})`);
console.log(`完了数: ${completed} (型: ${typeof completed})`);
console.log(`==での比較: ${isEqual}`);
console.log(`===での比較: ${isStrictEqual}`);
// 実際には同じ結果になる(どちらもnumber型のため)
console.log("この場合、どちらも同じ結果になります");
}
正解
function showDetailedStats() {
const total = tasks.length;
const completed = tasks.filter(task => task.completed).length;
// 等価演算子の比較(型変換あり)
const isEqual = total == completed;
// 厳密等価演算子の比較(型変換なし)
const isStrictEqual = total === completed;
console.log(`総タスク数: ${total} (型: ${typeof total})`);
console.log(`完了数: ${completed} (型: ${typeof completed})`);
console.log(`==での比較: ${isEqual}`);
console.log(`===での比較: ${isStrictEqual}`);
// 実際には同じ結果になる(どちらもnumber型のため)
console.log("この場合、どちらも同じ結果になります");
}
現代のJavaScript開発では、意図的な理由がない限り===
と!==
を使用することが強く推奨されています。これにより、型変換による予期しない動作を防ぎ、コードの意図がより明確になります。
==による問題の例:
// 問題のあるコード
if (userInput == 0) {
console.log("入力が0です");
}
// 問題:userInputが空文字""の場合もtrueになってしまう
// 改善されたコード
if (userInput === 0) {
console.log("入力が0です");
}
// 改善:厳密に数値の0のみをチェック
推奨される書き方:
===
と !==
を基本的に使用==
と !=
は特別な理由がある場合のみ===
の使用が推奨されるエラーとは、プログラムが正常に実行できない状況のことです。エラーが発生すると、プログラムはその時点で停止し、後続の処理は実行されません。エラーを適切に処理することで、プログラムの安定性を保ち、ユーザーに分かりやすいメッセージを提供できます。
エラーが発生する典型的な状況:
例1: 存在しない配列要素へのアクセス
let tasks = ["買い物", "掃除"];
console.log(tasks[10]); // undefined(エラーではない)
console.log(tasks[10].title); // TypeError(エラー発生)
例2: nullやundefinedのプロパティアクセス
let task = null;
console.log(task.title); // TypeError: Cannot read property 'title' of null
下の状況でエラーが発生するコードを特定してください:
let data = undefined;
let result1 = data + 5; // A: 計算実行
let result2 = data.length; // B: プロパティアクセス
let result3 = data[0]; // C: 配列アクセス
どの行でエラーが発生しますか? ___
正解
どの行でエラーが発生しますか? B と C
解説:
undefined + 5
= NaN
(エラーではない)undefined.length
= TypeError(エラー)undefined[0]
= TypeError(エラー)JavaScriptでエラーが発生すると、その時点でプログラムの実行が停止します。エラーの後に書かれたコードは実行されません。この動作により、ユーザーにとって不親切な状況や、データの破損などが起こる可能性があります。
エラー発生時の動作例:
console.log("プログラム開始");
// ここでエラーが発生
let invalidArray = null;
console.log(invalidArray.length); // TypeError が発生
console.log("この行は実行されない"); // エラーで停止するため
実行結果:
プログラム開始
TypeError: Cannot read property 'length' of null
// "この行は実行されない" は表示されない
try-catch文は、エラーが発生する可能性のあるコードを安全に実行するための制御構文です。tryブロック内でエラーが発生すると、プログラムを停止させる代わりに、catchブロックでエラー処理を行い、プログラムの実行を継続できます。
基本構文:
try {
// エラーが発生する可能性のあるコード
} catch (error) {
// エラーが発生した場合の処理
}
エラーハンドリングなしの場合:
console.log("処理開始");
let data = null;
console.log(data.length); // ここでエラーが発生してプログラム停止
console.log("この行は実行されない");
下のコードにtry-catch文を追加してプログラムが継続実行されるようにしてください:
console.log("処理開始");
___
let data = null;
console.log(data.length);
___ (error) {
console.log("エラーを処理しました");
___
console.log("処理継続");
正解
console.log("処理開始");
try {
let data = null;
console.log(data.length);
} catch (error) {
console.log("エラーを処理しました");
}
console.log("処理継続");
throw
文は、プログラマーが意図的にエラーを発生させるための構文です。不正な条件を検出した場合に、適切なエラーメッセージと共にエラーを発生させ、呼び出し元に問題を通知することができます。
throw文の基本構文:
throw new Error("エラーメッセージ");
実際の使用例:
function divide(a, b) {
if (b === 0) {
throw new Error("0で割ることはできません");
}
return a / b;
}
try {
let result = divide(10, 0); // エラーが発生
console.log(result);
} catch (error) {
console.log("計算エラー:", error.message);
}
配列の要素にアクセスする際の安全性を確保する方法を学習しましょう。
配列のインデックスアクセスでは、存在しない要素にアクセスしようとするとundefinedが返されます。undefinedのプロパティにアクセスしようとするとエラーが発生するため、事前にチェックが必要です。
安全でない例:
function getTaskTitle(index) {
return tasks[index].title; // tasks[index]がundefinedの場合エラー
}
安全な例(if文による事前チェック):
function getTaskTitle(index) {
if (index >= 0 && index < tasks.length) {
return tasks[index].title;
} else {
return "無効なインデックス";
}
}
安全な例(try-catchによるエラー処理):
function getTaskTitle(index) {
try {
if (index < 0 || index >= tasks.length) {
throw new Error(`無効なインデックス: ${index}`);
}
return tasks[index].title;
} catch (error) {
console.log("エラー:", error.message);
return null;
}
}
実際のTodoアプリでエラーハンドリングを実装してみましょう。
要件:
function deleteTask(index) {
___ {
if (index < 0 || index >= tasks.length) {
___ new Error("無効なインデックスです");
}
if (!tasks[index]) {
___ new Error("タスクが存在しません");
}
tasks.splice(index, 1);
console.log("タスクを削除しました");
} ___ (error) {
console.log("削除エラー:", error.message);
}
}
正解
function deleteTask(index) {
try {
if (index < 0 || index >= tasks.length) {
throw new Error("無効なインデックスです");
}
if (!tasks[index]) {
throw new Error("タスクが存在しません");
}
tasks.splice(index, 1);
console.log("タスクを削除しました");
} catch (error) {
console.log("削除エラー:", error.message);
}
}
JavaScriptのエラーオブジェクトには、エラーに関する詳細な情報が含まれています。主要なプロパティとしてmessage
(エラーメッセージ)、name
(エラーの種類)、stack
(エラーが発生した場所の情報)があります。
エラーオブジェクトのプロパティ:
try {
let result = nonExistentFunction();
} catch (error) {
console.log("エラー名:", error.name); // "ReferenceError"
console.log("メッセージ:", error.message); // "nonExistentFunction is not defined"
console.log("スタック:", error.stack); // 詳細な発生場所
}
カスタムエラーメッセージの活用:
function validateTaskInput(title) {
try {
if (!title) {
throw new Error("タスクタイトルが入力されていません");
}
if (title.length > 100) {
throw new Error("タスクタイトルが長すぎます(100文字以内)");
}
if (title.trim() === "") {
throw new Error("空白のみのタイトルは無効です");
}
return true;
} catch (error) {
console.log("入力検証エラー:", error.___);
return false;
}
}
下の穴埋めを完成させてください:
正解
function validateTaskInput(title) {
try {
if (!title) {
throw new Error("タスクタイトルが入力されていません");
}
if (title.length > 100) {
throw new Error("タスクタイトルが長すぎます(100文字以内)");
}
if (title.trim() === "") {
throw new Error("空白のみのタイトルは無効です");
}
return true;
} catch (error) {
console.log("入力検証エラー:", error.message);
return false;
}
}
適切なエラーハンドリングにより、ユーザーにとって使いやすく、開発者にとってデバッグしやすいプログラムを作成できます。エラーメッセージは分かりやすく、具体的で、ユーザーが次に何をすべきかが明確になるように書きます。
良いエラーメッセージの例:
// 悪い例
throw new Error("エラー");
// 良い例
throw new Error("タスク番号は1から10の間で入力してください(入力値: 15)");
エラーハンドリングの原則:
Node.jsは、JavaScriptをブラウザの外で実行できる環境です。様々な機能が「モジュール」として提供されており、require
文を使って必要な機能を読み込みます。モジュールシステムにより、プログラムを機能ごとに分割し、再利用可能にできます。
Node.jsモジュールの種類:
組み込みモジュール: Node.jsに最初から含まれているモジュール
readline
: コマンドライン入力fs
: ファイル操作path
: パス操作サードパーティモジュール: npmでインストールできる外部モジュール
express
: Webサーバーlodash
: ユーティリティ関数requireの基本構文:
const モジュール名 = require("モジュール名");
readlineは、Node.jsでコマンドライン入力を処理するための組み込みモジュールです。キーボードからの入力を受け取り、プログラムとユーザーが対話できるインターフェースを提供します。CLIアプリケーション開発において必須のモジュールです。
readlineの基本的な役割:
requireによるモジュール読み込み:
const readline = require("readline");
下のreadlineインターフェース作成コードを完成させてください:
const readline = require("readline");
const rl = readline.___({
input: process.___, // キーボード入力
output: process.___ // 画面出力
});
正解
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin, // キーボード入力
output: process.stdout // 画面出力
});
process.stdin
とprocess.stdout
は、Node.jsにおける標準入出力ストリームです。stdin
(Standard Input)はキーボードからの入力を、stdout
(Standard Output)は画面への出力を表します。これらは UNIX系OSの概念をJavaScriptで利用できるようにしたものです。
標準入出力の流れ:
ユーザー(キーボード入力)→ process.stdin → プログラム → process.stdout → 画面表示
実際の使用例:
// ユーザーが "Hello" と入力
// ↓ process.stdin で受け取り
// ↓ プログラムで処理
// ↓ process.stdout で出力
// → 画面に "こんにちは、Hello さん!" と表示
rl.question
は、ユーザーに質問を表示し、入力を待って、入力が完了したら指定した関数(コールバック関数)を実行するメソッドです。非同期処理の一種で、プログラムは入力を待っている間も他の処理を継続できます。
rl.questionの基本構文:
rl.question("質問メッセージ", (回答) => {
// 回答を受け取った後の処理
});
動作の流れ:
実際の例:
rl.question("お名前を入力してください: ", (name) => {
console.log(`こんにちは、${name}さん!`);
});
map
は、配列の各要素を変換して新しい配列を作成するメソッドです。元の配列は変更されず、各要素に対して指定した変換処理を行った結果として新しい配列が返されます。メニューの表示など、データの変換において頻繁に使用される重要なメソッドです。
mapの基本構文:
新しい配列 = 元の配列.map(要素 => 変換処理);
mapの基本例:
let numbers = [1, 2, 3];
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
インデックスも使用する例:
let fruits = ["りんご", "バナナ"];
let numberedFruits = fruits.map((fruit, index) => `${index + 1}. ${fruit}`);
console.log(numberedFruits); // ["1. りんご", "2. バナナ"]
下の穴埋めを完成させてください:
let items = ["タスク追加", "タスク削除"];
let menuItems = items.___((___, index) => `${___ + 1}. ${___}`);
正解
let items = ["タスク追加", "タスク削除"];
let menuItems = items.map((item, index) => `${index + 1}. ${item}`);
CLIアプリケーションでは、ユーザーが選択できる操作をメニュー形式で表示するのが一般的です。配列で選択肢を管理し、mapメソッドで番号付きに変換して表示することで、ユーザーにとって分かりやすいインターフェースを提供できます。
メニュー設計の原則:
メニュー配列の作成:
const MENU_OPTIONS = [
"タスク追加", "一覧(forEach)", "一覧(for)", "一覧(while)",
"完了", "削除", "検索", "統計", "プロパティ", "終了"
];
番号付きメニューの表示:
// 配列を番号付きの文字列に変換
let menuText = MENU_OPTIONS.___((___, index) => `${index + 1}. ${___}`);
console.log(menuText.join(" "));
下の穴埋めを完成させてください:
正解
// 配列を番号付きの文字列に変換
let menuText = MENU_OPTIONS.map((item, index) => `${index + 1}. ${item}`);
console.log(menuText.join(" "));
join
は、配列の要素を指定した区切り文字で結合して、一つの文字列を作成するメソッドです。メニューの表示や、データの出力において頻繁に使用される重要なメソッドです。
joinメソッドの基本構文:
配列.join("区切り文字")
joinメソッドの例:
let items = ["りんご", "バナナ", "オレンジ"];
console.log(items.join(", ")); // "りんご, バナナ, オレンジ"
console.log(items.join(" | ")); // "りんご | バナナ | オレンジ"
console.log(items.join("\n")); // りんご(改行)バナナ(改行)オレンジ
下の穴埋めを完成させてください:
let menuItems = ["1. タスク追加", "2. タスク削除"];
let menuString = menuItems.___(" ");
console.log(menuString); // "1. タスク追加 2. タスク削除" と表示される
正解
let menuItems = ["1. タスク追加", "2. タスク削除"];
let menuString = menuItems.join(" ");
console.log(menuString); // "1. タスク追加 2. タスク削除" と表示される
mapメソッドとjoinメソッドを組み合わせることで、配列から見やすいメニューを作成できます。
function showMenu() {
// 配列を番号付きの文字列に変換してから結合
let menuString = MENU_OPTIONS.___((item, i) => `${i + 1}. ${item}`).___(" ");
console.log("\n" + menuString);
rl.question("操作を選んでください: ", (answer) => {
handleMenuSelection(answer.trim());
});
}
下の穴埋めを完成させてください:
正解
function showMenu() {
// 配列を番号付きの文字列に変換してから結合
let menuString = MENU_OPTIONS.map((item, i) => `${i + 1}. ${item}`).join(" ");
console.log("\n" + menuString);
rl.question("操作を選んでください: ", (answer) => {
handleMenuSelection(answer.trim());
});
}
trim
は、文字列の前後にある空白文字(スペース、タブ、改行など)を除去するメソッドです。ユーザー入力の処理において、意図しない空白による問題を防ぐために重要です。
trimが必要な理由:
// ユーザーが間違えて " 1 " と入力(前後にスペース)
let userInput = " 1 ";
console.log(userInput === "1"); // false(空白があるため)
console.log(userInput.trim() === "1"); // true(空白が除去される)
下の穴埋めを完成させてください:
rl.question("番号を選択: ", (answer) => {
let cleanAnswer = answer.___(); // 前後の空白を除去
handleMenuSelection(cleanAnswer);
});
正解
rl.question("番号を選択: ", (answer) => {
let cleanAnswer = answer.trim(); // 前後の空白を除去
handleMenuSelection(cleanAnswer);
});
switch文は、一つの変数の値に対して複数の条件分岐を効率的に処理するための制御構文です。多数のif-else文を書くよりも読みやすく、メニュー選択処理のような場面で威力を発揮します。各case文の最後にはbreak文が必要です。
switch文の基本構造:
switch (変数) {
case "値1":
// 処理1
break;
case "値2":
// 処理2
break;
default:
// どの値にも一致しない場合
}
break文の重要性:
switch (value) {
case "1":
console.log("1が選択");
// breakがないと次のcaseも実行される
case "2":
console.log("2が選択");
break;
}
下の多分岐をswitch文で実装してください:
function processNumber(num) {
___ (num) {
___ "1":
console.log("一");
___;
___ "2":
console.log("二");
___;
___:
console.log("その他");
}
}
正解
function processNumber(num) {
switch (num) {
case "1":
console.log("一");
break;
case "2":
console.log("二");
break;
default:
console.log("その他");
}
}
JavaScriptの非同期処理では、処理の完了を待たずに次の処理に進みます。rl.question
は非同期関数で、ユーザーの入力完了後にコールバック関数が実行されます。連続した入力が必要な場合は、コールバック関数の中で次のrl.question
を呼び出す入れ子構造になります。
コールバック関数の仕組み:
console.log("1. プログラム開始");
rl.question("質問1: ", (answer1) => {
console.log("3. 回答1を受け取り:", answer1);
rl.question("質問2: ", (answer2) => {
console.log("5. 回答2を受け取り:", answer2);
});
console.log("4. 質問2を表示");
});
console.log("2. 質問1を表示");
実行順序:
複数の項目を順番に入力する処理を実装しましょう。
function promptAddTask() {
rl.question("タスク名: ", (title) => {
rl.question("説明: ", (desc) => {
rl.question("優先度(数字): ", (priority) => {
rl.question("重要? (true/false): ", (important) => {
// すべての入力が完了した時点で実行
const newTask = createTask(
title,
desc,
priority,
important === "___"
);
tasks.push(newTask);
console.log(`タスクを追加しました: ${newTask.title}`);
showMenu();
});
});
});
});
}
下の穴埋めを完成させてください:
正解
function promptAddTask() {
rl.question("タスク名: ", (title) => {
rl.question("説明: ", (desc) => {
rl.question("優先度(数字): ", (priority) => {
rl.question("重要? (true/false): ", (important) => {
// すべての入力が完了した時点で実行
const newTask = createTask(
title,
desc,
priority,
important === "true"
);
tasks.push(newTask);
console.log(`タスクを追加しました: ${newTask.title}`);
showMenu();
});
});
});
});
}
rl.close()
は、readlineインターフェースを閉じ、プログラムを正常に終了させるメソッドです。CLIアプリケーションでは、ユーザーが「終了」を選択した時に呼び出し、リソースを適切に解放します。
プログラム終了の流れ:
rl.close()
を実行適切な終了処理:
case "10":
console.log("アプリケーションを終了します...");
rl.___();
break;
下の穴埋めを完成させてください:
正解
case "10":
console.log("アプリケーションを終了します...");
rl.close();
break;
typeof演算子は、変数や値のデータ型を文字列で返す演算子です。プログラム実行時に型を確認し、型に応じた適切な処理を行うために使用します。エラーを防ぎ、安全なプログラムを作成するための重要なツールです。
typeof演算子の基本構文:
typeof 変数名
typeof 値
主要な型の確認:
console.log(typeof 25); // "number"
console.log(typeof "text"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
下の型確認コードを完成させてください:
let age = 30;
let name = "田中";
let isActive = false;
console.log("age の型:", ___ age);
console.log("name の型:", ___ name);
console.log("isActive の型:", ___ isActive);
正解
let age = 30;
let name = "田中";
let isActive = false;
console.log("age の型:", typeof age);
console.log("name の型:", typeof name);
console.log("isActive の型:", typeof isActive);
ユーザーからの入力は常に文字列として受け取られます。数値として使用する場合は、事前に型変換を行い、変換が正常に行われたかをチェックする必要があります。
安全な数値変換の例:
function safeNumberConversion(input) {
// 文字列を数値に変換
let number = Number(input);
// NaN(変換失敗)をチェック
if (typeof number === "number" && !isNaN(number)) {
return number;
} else {
return null; // 変換失敗
}
}
// 使用例
let userInput = "25";
let taskIndex = safeNumberConversion(userInput); // 25
let invalidInput = safeNumberConversion("abc"); // null
タスク番号の安全な処理:
function processTaskIndex(input) {
let index = Number(input) - 1; // 1ベースから0ベースに変換
if (___ index !== "number" || isNaN(index)) {
return null; // 数値でない
}
if (index < 0 || index >= tasks.length) {
return null; // 範囲外
}
return index; // 有効なインデックス
}
下の穴埋めを完成させてください:
正解
function processTaskIndex(input) {
let index = Number(input) - 1; // 1ベースから0ベースに変換
if (typeof index !== "number" || isNaN(index)) {
return null; // 数値でない
}
if (index < 0 || index >= tasks.length) {
return null; // 範囲外
}
return index; // 有効なインデックス
}
Object.entries()は、オブジェクトの各プロパティを [キー, 値] の配列形式で返すメソッドです。オブジェクトの全プロパティを順番に処理したい場合に使用します。返される配列は、プロパティの数だけ要素を持ちます。
基本的な動作:
let task = {
title: "買い物",
completed: false
};
let entries = Object.entries(task);
console.log(entries);
// [ ["title", "買い物"], ["completed", false] ]
配列の各要素は [キー, 値] の形式:
console.log(entries[0]); // ["title", "買い物"]
console.log(entries[1]); // ["completed", false]
下のオブジェクトをObject.entries()で変換してください:
let user = {
id: 1,
name: "山田"
};
let userEntries = Object.___(user);
console.log(userEntries); // [ ["id", 1], ["name", "山田"] ] と表示される
正解
let user = {
id: 1,
name: "山田"
};
let userEntries = Object.entries(user);
console.log(userEntries); // [ ["id", 1], ["name", "山田"] ] と表示される
分割代入は、配列から要素を取り出して複数の変数に一度に代入する構文です。Object.entries()が返す [キー, 値] の配列に対して使用することで、キーと値を別々の変数として同時に取得できます。
配列の分割代入の基本:
let data = ["apple", "red"];
let [name, color] = data;
console.log(name); // "apple"
console.log(color); // "red"
従来の方法と分割代入の比較:
let entry = ["title", "買い物"];
// 従来の方法
let key = entry[0];
let value = entry[1];
// 分割代入
let [key, value] = entry;
下の配列を分割代入で取り出してください:
let userInfo = ["田中", 30];
let [___, ___] = userInfo;
console.log(`名前: ${userName}, 年齢: ${userAge}`);
正解
let userInfo = ["田中", 30];
let [userName, userAge] = userInfo;
console.log(`名前: ${userName}, 年齢: ${userAge}`);
Object.entries()、分割代入、typeof演算子を組み合わせて、プロパティ表示機能を実装します。
複数の概念を組み合わせることで、より実用的で高機能なプログラムを作成できます。Object.entries()でプロパティを取得し、分割代入でキーと値を分離し、typeof演算子で型情報を表示する総合的な機能を実装します。
統合する3つの要素:
下の関数を完成させてください:
function showTaskProperties(index) {
if (typeof index !== "number" || index < 0 || index >= tasks.length) {
console.log("無効なインデックスです");
return;
}
const task = tasks[index];
console.log("タスクのプロパティ:");
// 3つの要素を全て使って実装
for (const [___, ___] of Object.___(___)) {
console.log(` ${key}: ${value} (型: ${___ value})`);
}
}
正解
function showTaskProperties(index) {
if (typeof index !== "number" || index < 0 || index >= tasks.length) {
console.log("無効なインデックスです");
return;
}
const task = tasks[index];
console.log("タスクのプロパティ:");
// 3つの要素を全て使って実装
for (const [key, value] of Object.entries(task)) {
console.log(` ${key}: ${value} (型: ${typeof value})`);
}
}
ユーザーからの入力を受け取って、プロパティ表示機能を呼び出す処理を実装しましょう。
function promptShowProperties() {
rl.question("プロパティを表示するタスク番号: ", (num) => {
const index = Number(num) - 1;
showTaskProperties(index);
showMenu();
});
}
タスク完了機能では、指定されたタスクのcompleted
プロパティをtrue
に変更します。既に完了済みのタスクや、存在しないタスクに対する適切なエラーハンドリングも重要です。
タスク完了機能の要件:
function completeTask(index) {
// タスクの存在確認と未完了確認
if (tasks[index] && !tasks[index].completed) {
tasks[index].___ = true;
console.log("タスクを完了しました!");
} else if (tasks[index] && tasks[index].completed) {
console.log("このタスクは既に完了済みです");
} else {
console.log("無効なタスク番号です");
}
}
下の穴埋めを完成させてください:
正解
function completeTask(index) {
// タスクの存在確認と未完了確認
if (tasks[index] && !tasks[index].completed) {
tasks[index].completed = true;
console.log("タスクを完了しました!");
} else if (tasks[index] && tasks[index].completed) {
console.log("このタスクは既に完了済みです");
} else {
console.log("無効なタスク番号です");
}
}
各機能に対応する入力処理関数を実装します。
// タスク完了の入力処理
function promptCompleteTask() {
rl.question("完了にするタスク番号: ", (num) => {
const index = Number(num) - 1;
completeTask(index);
showMenu();
});
}
// タスク削除の入力処理
function promptDeleteTask() {
rl.question("削除するタスク番号: ", (num) => {
const index = Number(num) - 1;
deleteTask(index);
showMenu();
});
}
// タスク検索の入力処理
function promptSearchTask() {
rl.question("検索ワード: ", (word) => {
searchTasks(word);
showMenu();
});
}
すべての機能をメニューに統合しましょう。
function handleMenuSelection(selection) {
switch (selection) {
case "1":
promptAddTask();
break;
case "2":
listTasksForEach();
showMenu();
break;
case "3":
listTasksFor();
showMenu();
break;
case "4":
listTasksWhile();
showMenu();
break;
case "5":
promptCompleteTask();
break;
case "6":
promptDeleteTask();
break;
case "7":
promptSearchTask();
break;
case "8":
showStatistics();
showMenu();
break;
case "9":
promptShowProperties();
break;
case "10":
console.log("アプリケーションを終了します...");
rl.close();
break;
default:
console.log("1~10の番号で選択してください");
showMenu();
}
}
アプリケーションのエントリーポイントは、プログラムが開始される最初の処理部分です。グローバル変数の初期化、ウェルカムメッセージの表示、メイン処理の開始を順番に行います。ユーザーが最初に見る部分なので、分かりやすい説明が重要です。
初期化処理の順序:
下のアプリケーション起動コードを完成させてください:
// 1. グローバル変数の初期化
let tasks = [];
// 2. ウェルカムメッセージ
console.log("=== Todo CLI アプリケーション ===");
console.log("JavaScript を使った実践的な CLI ツール");
// 3. メイン処理開始
___();
正解
// 1. グローバル変数の初期化
let tasks = [];
// 2. ウェルカムメッセージ
console.log("=== Todo CLI アプリケーション ===");
console.log("JavaScript を使った実践的な CLI ツール");
// 3. メイン処理開始
showMenu();
完成したアプリケーションのファイル構成を確認しましょう。
ファイルの構成順序:
require
文MENU_OPTIONS
などtasks
配列createTask
などlistTasks
, deleteTask
などpromptAddTask
などshowMenu
, handleMenuSelection
この構成により、読みやすく保守しやすいコードが完成します。
✅ 完成確認
これですべての機能が実装されました。以下のコマンドでアプリケーションを実行できます:
node todo.js
基本構文
配列操作
制御構文
オブジェクト指向
エラーハンドリング
Node.js特有機能
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const MENU = ["タスク追加", "一覧(forEach)", "一覧(for)", "一覧(while)", "完了", "削除", "検索", "統計", "プロパティ", "終了"];
let tasks = [];
function createTask(title, desc = null, priority = 1, important = false) {
return {
title: title || "無題",
desc: desc ?? "説明なし",
priority: Number(priority),
important: Boolean(important),
completed: false,
created: new Date(),
};
}
function addTask(task) {
tasks.push(task);
}
function listTasks(mode = "forEach") {
if (tasks.length === 0) {
console.log("タスクがありません");
return;
}
if (mode === "forEach") {
tasks.forEach((t, i) => {
console.log(`${i + 1}: [${t.completed ? "x" : " "}] ${t.title} (優先度:${t.priority}, 重要:${t.important ? "!" : "-"})`);
});
} else if (mode === "for") {
for (let i = 0; i < tasks.length; i++) {
const t = tasks[i];
console.log(`${i + 1}: [${t.completed ? "x" : " "}] ${t.title}`);
}
} else if (mode === "while") {
let i = 0;
while (i < tasks.length) {
const t = tasks[i];
console.log(`${i + 1}: [${t.completed ? "x" : " "}] ${t.title}`);
i++;
}
}
}
function completeTask(idx) {
if (tasks[idx] && !tasks[idx].completed) {
tasks[idx].completed = true;
console.log("完了にしました!");
} else {
console.log("無効な番号または既に完了済み");
}
}
function deleteTask(idx) {
try {
if (tasks[idx]) {
tasks.splice(idx, 1);
console.log("削除しました!");
} else {
throw new Error("無効な番号");
}
} catch (e) {
console.log("エラー:", e.message);
}
}
function searchTasks(word) {
const found = tasks.filter((t) => t.title.includes(word));
if (found.length > 0) {
found.forEach((t, i) => console.log(`${i + 1}: [${t.completed ? "x" : " "}] ${t.title}`));
console.log(
"全て完了:",
found.every((t) => t.completed),
"未完了あり:",
found.some((t) => !t.completed)
);
} else {
console.log("該当タスクなし");
}
}
function showStats() {
const total = tasks.length;
const completed = tasks.filter((t) => t.completed).length;
const allCompleted = tasks.every((t) => t.completed);
const hasIncomplete = tasks.some((t) => !t.completed);
const eq = total == completed;
const strictEq = total === completed;
console.log(`総数: ${total}, 完了: ${completed}, 未完了: ${total - completed}`);
console.log(`全て完了: ${allCompleted ? "はい" : "いいえ"}, 未完了あり: ${hasIncomplete ? "はい" : "いいえ"}`);
console.log(`==: ${eq}, ===: ${strictEq}`);
}
function showTaskProperties(idx) {
if (typeof idx !== "number" || idx < 0 || idx >= tasks.length) {
console.log("無効な番号");
return;
}
const task = tasks[idx];
console.log("プロパティ一覧:");
for (const [key, value] of Object.entries(task)) {
console.log(` ${key}: ${value}`);
}
}
function promptAddTask() {
rl.question("タスク名: ", (title) => {
rl.question("説明: ", (desc) => {
rl.question("優先度(数字): ", (priority) => {
rl.question("重要? (true/false): ", (important) => {
const task = createTask(title, desc, priority, important === "true");
addTask(task);
console.log(`追加しました! (${task.title})`);
showMenu();
});
});
});
});
}
function promptCompleteTask() {
rl.question("完了にする番号: ", (num) => {
const idx = Number(num) - 1;
completeTask(idx);
showMenu();
});
}
function promptDeleteTask() {
rl.question("削除する番号: ", (num) => {
const idx = Number(num) - 1;
deleteTask(idx);
showMenu();
});
}
function promptSearchTask() {
rl.question("検索ワード: ", (word) => {
searchTasks(word);
showMenu();
});
}
function promptShowProperties() {
rl.question("プロパティを見たいタスク番号: ", (num) => {
showTaskProperties(Number(num) - 1);
showMenu();
});
}
function showMenu() {
console.log("\n" + MENU.map((item, i) => `${i + 1}. ${item}`).join(" "));
rl.question("操作を選んでください: ", (answer) => {
switch (answer.trim()) {
case "1":
promptAddTask();
break;
case "2":
listTasks("forEach");
showMenu();
break;
case "3":
listTasks("for");
showMenu();
break;
case "4":
listTasks("while");
showMenu();
break;
case "5":
promptCompleteTask();
break;
case "6":
promptDeleteTask();
break;
case "7":
promptSearchTask();
break;
case "8":
showStats();
showMenu();
break;
case "9":
promptShowProperties();
break;
case "10":
rl.close();
break;
default:
console.log("1~10の番号で選択してください");
showMenu();
}
});
}
// コメント例
// これは単一行コメントです
/*
これは複数行コメントです。
文法事項の説明やサンプルをここに書けます。
*/
showMenu();
これで、JavaScript の基礎から実践的なCLIアプリケーション開発まで、体系的に学習することができました!
console.log
は、プログラムの動作を確認するための最も基本的なデバッグツールです。変数の値、関数の実行タイミング、エラーの原因を特定するために使用します。効果的に使うことで、問題を素早く発見し、解決できます。
デバッグのためのconsole.log
テクニック:
// 変数の値を確認
let userName = "田中";
console.log("ユーザー名:", userName);
// 関数の開始と終了を確認
function addTask(title) {
console.log("addTask関数開始 - タイトル:", title);
// 処理...
console.log("addTask関数終了");
}
// オブジェクトの中身を確認
let task = {title: "買い物", completed: false};
console.log("タスクオブジェクト:", task);
プログラミングを始めたばかりの時は、よく似たエラーに遭遇します。これらのエラーパターンを覚えておくことで、同じ問題に直面した時に迅速に解決できます。
頻出エラー一覧:
エラーメッセージ | 原因 | 解決方法 |
---|---|---|
ReferenceError: xxx is not defined |
変数や関数が宣言されていない | 変数の宣言を確認、スペルミスをチェック |
TypeError: Cannot read property 'xxx' of undefined |
オブジェクトがundefinedまたはnull | オブジェクトの存在確認を追加 |
SyntaxError: Unexpected token |
文法エラー(括弧の不一致など) | 括弧、クォートの対応を確認 |
TypeError: xxx.push is not a function |
配列でない変数に配列メソッドを使用 | 変数の型を確認 |
実際のトラブルシューティング例:
// エラーが出る例
function displayTask(index) {
console.log(tasks[index].title); // エラー:tasks[index]がundefined
}
// 修正版
function displayTask(index) {
if (index >= 0 && index < tasks.length && tasks[index]) {
console.log(tasks[index].title);
} else {
console.log("無効なタスク番号です");
}
}
プログラムは書いた本人だけでなく、他の人も読むことを前提に書きます。また、数か月後の自分も「他の人」です。読みやすいコードを書くことで、バグの発見、機能追加、チーム開発が格段に楽になります。
読みやすいコードのルール:
意味のある変数名
// 悪い例
let d = new Date();
let u = "田中";
// 良い例
let currentDate = new Date();
let userName = "田中";
適切なコメント
// タスクの完了状態を切り替える関数
function toggleTaskCompletion(taskIndex) {
// 配列の範囲内かチェック
if (taskIndex >= 0 && taskIndex < tasks.length) {
tasks[taskIndex].completed = !tasks[taskIndex].completed;
}
}
関数の分割
// 長すぎる関数は分割する
function processUserInput(input) {
let cleanInput = sanitizeInput(input);
let validInput = validateInput(cleanInput);
return processValidInput(validInput);
}
既存のプログラムに新しい機能を追加する時は、既存のコードの構造を理解し、一貫性を保ちながら実装することが重要です。小さな変更から始めて、徐々に複雑な機能を追加していきます。
機能追加の手順:
実例: タスクの優先度変更機能を追加
// 既存の構造に合わせて新機能を追加
function changePriority(taskIndex, newPriority) {
try {
if (taskIndex >= 0 && taskIndex < tasks.length) {
tasks[taskIndex].priority = Number(newPriority);
console.log("優先度を変更しました");
} else {
throw new Error("無効なタスク番号です");
}
} catch (error) {
console.log("エラー:", error.message);
}
}
プログラミングの学習は継続が重要です。基礎を固めた後は、実際のプロジェクト作成、新しい技術の習得、コミュニティへの参加を通じて、継続的にスキルアップしていきます。
推奨学習ステップ:
基礎の反復練習
Web開発への展開
フレームワークの学習
実践的なプロジェクト
今回学習した内容は、実際の業務で以下のような形で活用できます。プログラミングの知識は、エンジニア以外の職種でも、作業の自動化や効率化に役立ちます。
業務での活用例:
作業自動化
データ分析
ツール開発
継続学習のためのリソース:
プログラミングスキルの向上には、継続的な練習が不可欠です。毎日少しずつでも、コードを書く習慣を身につけることで、着実にスキルアップできます。
効果的な学習習慣:
毎日のコーディング
コードレビューの習慣
実践的なプロジェクト
🎉 学習完了おめでとうございます!
真のプログラマーへの道のりは今から始まります。今回のガイドで学習した基礎知識を土台に、継続的な学習と実践を通じて、さらなるスキルアップを目指してください!
最終課題: 独自機能の追加 今回作成したTodoアプリに、以下のような独自機能を追加してみましょう:
プログラミングの世界はとても広く、常に新しい技術が生まれています。基礎をしっかりと身につけた今、どんな技術にも挑戦できる準備が整いました!