こんにちは。
本日から「JavaScript基礎習得メモ」を書いていきます。
経緯としては、かれこれ2年ほど、現職と平行し Web制作会社への転職を目指して、あっちこっちスキルに手を付けたり、ポートフォリオサイトをいくつか制作しましたが、やってみたいという強い思いとは裏腹、一向に'コードを書くこと'への苦手意識が消えず...。
そろそろ焦らないとまずいと思い、'フロントエンド'の方向に進みたいという前提で、MENTAにてプロの方へアドバイスを募ったところ、
「JavaScriptは一生稼げる1番の友達」
「React→Next.jsは現場全員が使っている超必修スキル」
との名言をいただきまして。モチベーションアップにつながりました。
今後の学習方法としては、
・ドットインストールのJavaScript動画を片っ端から見直し、出来る限り詳細に、どこが分かる/分からないを整理する(分からなくてもとりあえずメモ)。
・ドットインストール公式様が出しているnote記事「JavaScriptに挫折しそうな方への提案 〜 自分で書けるようになるまで」(以下URL)を参考にする。
...といった要領で進めていこうと思います。
https://note.com/dotinstall/n/n9e2c2eedc096
はじめに。注意。
偶然この記事にたどり着いた方、ごめんなさい。本当に拙いです。初歩の初歩状態です。間違っているところもあるかもしれません。勉強しながら継続して編集していきます。
はじめてのJavaScript(全7回)
- jQueryやBootstrapとは違い、自分の欲しい機能をとことんカスタマイズできるようになる。Node.js、Reactに展開可能
- タブメニューや計算ツールなど、細部までコードには触れないが、出来ることがたくさんあることが分かる序章
- 多少分からないことがあっても、先に進むべし
JavaScript入門 基礎文法編(全27回)
- ライブラリやフレームワークへ進む前に、基本的な文法をしっかりと確認しておくこと
-
alert(メッセージ);
、console.log(メッセージ);
→シンプル。不明点は特になし - 演算:
console.log(10 ** 3);
= 1000→べき乗。知らなかったので覚えておく
-
const
→定数の宣言に使われる(消費税の計算など)。=
で代入 - あとで値を再代入したい場合は
let
=「変数」で宣言する - 値が途中で変わっていたりするとコードが読みづらいため、基本的には
const
で宣言し、仕方ない時だけlet
にする。(var
は古いので使わない) -
値を変えないものは
const
、値を変える必要があるならlet
-
${...}
定数や変数を埋め込む(「テンプレートリテラル」という) - 「~と等しい」は
===
、「~と等しくない」は!==
を使う('='の数に注意) -
if (条件) {出力} else if (条件) {出力} else (条件) {出力}
お馴染みの条件分岐 -
右クリック→「ドキュメントのフォーマット」でインデントを綺麗に整理できる。(初耳でした。汗)
- スコアなど数値でなく、値が定まっている文字列で条件分岐させる場合は、
switch
を使う。switch (...) {case '...': console.log('...'); break; default: console.log('...'); break;}
-
for
:反復処理。条件を満たしてる段階まで処理を繰り返す。例:for (let price = 150; price <= 160; price++)
-
do...while
:最低1回は必ず処理を実行し、その後while
の条件を評価してループ継続するか判断する構文。例:ユーザー入力によるメニュー選択など、「最初に一度は案内を出したい」時('1~3からメニューを選んでくださいなどの状況')に使う
let command;
do {
command = Number(prompt('Menu 1, 2, 3 or 0 to exit'));
// do=初期メッセージとして必ず1度は表示させる
if (command === 0) {
console.log('終了します');
// 0を押すことで卓上メニュー端末の動作を終了
} else {
console.log(`メニュー${command}を処理中...`);
// お客様が1~3の中で食べたいものを選択中
}
} while (command !== 0);
// while:command が 0 でない限り、do の処理を繰り返す
// → 「0以外の数字(1~3)が入力されたらメニュー処理を継続」
// do...while文:最初の1回は必ず実行されるループ
// ユーザーにメニュー番号を入力させ、0が入力されるまで繰り返す
-
if
、for
、while
、do...while
の違い:-
if
:条件が「1回だけ」真かどうかを判断して、そのときだけ処理を実行する。→ 「ある条件のときに一度だけ処理したい」ときに使う
例:if (score > 80) { console.log('よくできました'); }
-
for
: 最初から「何回繰り返すか」が分かっているときに使う
例:150〜160円の値段で繰り返す→for (let price = 150; price <= 160; price++) { console.log("Price: " + price); }
-
while
:条件が「真の間、何度も繰り返し」処理を実行する。→ 「条件を満たす間、何度も処理を続けたい」ときに使う
例:let count = 0; while (count < 3) { console.log('処理中...'); count++;}
-
do...while
:条件に関係なく、まず1回だけ実行し、その後で条件を判定して繰り返す。→ 「とにかく1回は処理を実行したい、その後も条件次第で繰り返したい」ときに使う
例:ユーザーへ何度も入力を求めたいが、必ず最初に1回は聞く必要があるとき→let command; do { command = Number(prompt('Menu 1, 2, 3 or 0 to exit')); console.log(
メニュー${command}を処理中...); } while (command !== 0);
☆使い分け:よく使われるfor
を試してみて、うまくいかなかったらwhile
、それでもうまくいかなかったらdo...while
という使い分けで書いてみる
-
-
break
:反復処理全体から抜けるための命令 -
continue
:反復処理の途中でそれ以降の処理をスキップして、次の反復処理に移るための命令
-
if
を使うまでもない短い条件の場合は、三項演算子?
を使う
例:score < 30 ? 'Team A' : 'Team B'
は、score が30より小さいなら'Team A'、そうでなければ 'Team B'となり、scoreが40の場合、30未満ではない=false='Team B'が出力される。※複雑な条件や処理にはif
/else
を使うこと
-
論理演算子
&&
:なおかつ||
:または - スコープ:定数や変数の参照可能な範囲 → JavaScriptでは主に「グローバルスコープ」「ローカルスコープ」の2つがある
'use strict';
let x = 10;
{
let x = 20;
console.log(x);
}
// {}内はローカルスコープと呼ばれる。出力は20。
console.log(x);
// {}外はグローバルスコープと呼ばれる。出力は10。
※別のjsファイルでxを使いたい際にローカルスコープを使用する。
- 基礎文法編まとめ(感想)
関数などが出てきていないため、'本番っぽいコード'ではないものの、大切な基礎が詰まっていた。for
、while
、do...while
や、if
/else
と三項演算子?
の使い分けが難しく感じたが、こうした基礎が頭に入っていて、必要な時に思い出して使えるのが'書ける'という状態なのだなと感じた。
JavaScript入門 関数編(全12回)
- 関数は
return
を使って結果を返す。return
を省略すると、返り値はundefined
になる。console.log()
は「表示」するだけで、値を返していない。他の関数や処理で再利用したい場合は、必ずreturn
で返すこと
function sum(a, b) {
return a + b;
}
console.log(sum(3, 7) * 2); // → 20
- 引数のデフォルト値:関数に初期値を設定することで、引数が省略された場合にも安全に処理を行える。消費税率や送料など、オプション的な値を関数内で処理したいときに使う
'use strict';
{
function calculateTotal(price, amount, rate = 1.1) {
return price * amount * rate;
}
console.log(calculateTotal(100, 10)); // → 1100(rateは1.1)
console.log(calculateTotal(150, 10)); // → 1650(rateは1.1)
console.log(calculateTotal(200, 10)); // → 2200(rateは1.1)
console.log(calculateTotal(120, 10, 1.08)); // → 1296(rateを上書き)
}
-
早期リターン:条件に合えばすぐ
return
して、それ以降の処理をスキップする書き方。if
~return
で無駄な処理を省いてコードをスッキリ書ける
'use strict';
{
function calcurateTotal(price, amount, rate = 1.1) {
// 早期リターン実装前
// if (amount >= 100) {
// return price * amount;
// } else {
// return price * amount * rate;
// }
if (amount >= 100) {
// 早期リターン
return price * amount;
}
return price * amount * rate;
}
console.log(calcurateTotal(100, 100));
console.log(calcurateTotal(1000, 10));
- 引数のスコープ:JavaScriptでは、関数の引数や中で定義した変数は、その関数の中(スコープ)でしか使えず、関数の外で同じ名前を使おうとすると、エラーになる
'use strict';
{ // ←この{}は「スコープブロック」と言って関数ではないので注意!=外!
function double(num) {
// ← 「中!」:この中だけで num は使える
return num * 2;
}
function triple(num) {
return num * 3; // ← ここも「中!」だから num は使える
}
console.log(double(10)); // → 20
// ↓↓↓ 「外!」で num を使おうとするとエラー!
// console.log(num); // エラー:num is not defined
console.log(triple(20)); // → 60
}
- 実践すべき技:同じ処理を関数にまとめることで、コードの重複を減らし、保守性も高くなる
'use strict';
{
function showAd() {
console.log("---------");
console.log("SALE! 50% OFF!");
console.log("---------");
}
function showContent() {
console.log("BREAKING NEWS!");
console.log("Two baby pandas born at our Zoo!");
}
showAd(); // 同じ広告表示を関数で共通化!
showContent();
showAd();
}
-
関数宣言と関数式:JavaScriptでは、関数を「関数宣言」と「関数式」という2つの方法で定義できる
まず 「関数宣言」 は、関数を先に呼び出しても動作する。これは「ホイスティング」という、コードのどこに宣言を書いても実行時には、コードの先頭へ書いたことになる仕組み(=関数の巻き上げ)があるため。 - 一方 「関数式」 は、関数を変数に代入する形で定義する。こちらは通常の変数と同じ扱いになるため、定義より前に呼び出すとエラーになる
// 関数宣言:先に呼び出してもOK
'use strict';
{
console.log(double(5)); // 呼び出しOK!
function double(num) {
return num * 2;
}
}
※function double(num)
のような 関数宣言は、定義より前に呼び出してもエラーにならない。
// 関数式:先に呼ぶとエラー!
'use strict';
{
console.log(double(5)); // エラー!double is not defined
const double = function(num) {
return num * 2;
};
}
このように、関数式(const
に代入する形)で定義された関数は、定義の前に呼び出すとエラーになる。
- 最初は「関数宣言」を使うと安心
- 「関数式」はアロー関数などとセットでよく使われる+“定義してから使う”が鉄則!
// 安心して使える関数宣言(初心者にやさしい)
function greet(name) {
return `Hello, ${name}`;
}
console.log(greet("Alice"));
// 実務ではこちらが主流(アロー関数+関数式)
const greet = (name) => `Hello, ${name}`;
console.log(greet("Bob"));
//どちらも書けるようにしておく!!
- 重要!:関数を引数に渡す「コールバック関数」
- 「関数を引数に渡す」とは?
関数の中で、別の関数を実行したい時に、引数として関数を渡すテクニック。「何をどう処理するか」を あとから決めたいときに便利!
// コールバック関数:関数を引数に渡すことで、あとから処理内容を柔軟に決められる
const calc = (num, func) => {
return func(num);
};
console.log(calc(20, (n) => n * 2)); // => 40
- ※「コールバック関数」は関数の“用途”の分類
- ※「アロー関数」は関数の“書き方”の一種
☆ なぜ大事か?どこで使うか?
Web制作やJS実務で、以下のような場面で頻出。
用途 | 使われる場所の例 |
---|---|
イベント処理 | addEventListener("click", handler) |
配列の処理 |
arr.map(func) や forEach(func)
|
APIのレスポンス処理 | fetch().then(response => {...}) |
アニメーション制御 | setTimeout(() => {...}, 1000) |
-
Q.何が一番使うのか?
→ 場面によって違うが、確実に使う頻度が高いのはアロー関数 + コールバック関数の組み合わせ である!
この書き方は、React、jQuery、API操作、配列操作、アニメーション処理など、JS実務で“ほぼ確実に登場” する。
- 関数編まとめ(感想)
難しさは引き続き感じたが、見たことのあるコードが登場し、実務で'ここが特に使う'といったレベル感が整理できて良かった。あとから最も復習すべきセクションとなったかもしれない。
関数と構文を「合わせて使う」実務的な例
関数 ✕ if(条件によって処理を分けたい)
const greet = (name) => {
if (!name) {
return '名前がありません';
}
return `こんにちは、${name}さん`;
};
console.log(greet('太郎')); // => こんにちは、太郎さん
console.log(greet('')); // => 名前がありません
-
if
で条件分岐し、関数の中で動的に出力を変えてる例 - フォーム入力チェックなど実務でよくあるパターン!
関数 ✕ for(繰り返しの処理をまとめたい)
const printScores = (scores) => {
for (let i = 0; i < scores.length; i++) {
console.log(`${i + 1}人目の点数: ${scores[i]}`);
}
};
printScores([70, 80, 90]);
// => 1人目の点数: 70
// => 2人目の点数: 80
// => 3人目の点数: 90
-
for
のループ処理を関数にまとめることで、再利用性UP! - 配列の値を順番に処理する基本中の基本
関数 ✕ forEach(配列の要素を順に処理)
const showFruits = (arr) => {
arr.forEach((fruit, index) => {
console.log(`${index + 1}個目の果物:${fruit}`);
});
};
showFruits(['りんご', 'みかん', 'バナナ']);
// => 1個目の果物:りんご
// => 2個目の果物:みかん
// => 3個目の果物:バナナ
-
forEach
は関数(アロー関数)を中に入れて使う構文 - コールバック関数の練習にもなる!
JavaScript入門 データ構造編(全27回)
- 配列の基礎
'use strict';
{
const scores = [
70,
90,
80,
85,
// 値を追加することもあるので、最後の値はカンマがあってもいい。
];
console.log(scores[2]); //80
console.log(scores.length); //個数。出力結果は4。
scores[1] = 95;
console.log(scores); // [70, 95, 80, 85]
}
-
length
は要素の個数 を表す
-
const
の値を後から書き換えると通常はエラーになるが、配列の場合は例外で、後から書き換えが出来る。但しこの場合、const
以外を使うと上手くいかないため注意!
- 配列の末尾に要素を追加=
push
、配列の末尾の要素を削除=pop
- 配列の先頭に要素を追加=
unshift
、配列の先頭の要素を削除=shift
→push
はよく使う。それ以外は必要に応じて復習する
for
:配列の中身を順に取り出して処理する
'use strict';
{
const scores = [
70,
90,
80,
85,
];
scores.push(77, 88);
// 要素取得
// console.log(scores[0]);
// console.log(scores[1]);
// console.log(scores[2]);
// console.log(scores[3]);
// ↑ この意味を成すコードを、forで書く!
for (let i = 0; i < scores.length; i++) {
console.log(scores[i]);
}
// `.length`の部分 → 定着させるべきオススメの書き方
// (`i<4`とか書くよりも保守性が高い)
}
forEach
:途中にアロー関数を使う。for文よりもコード量が少なく、配列の処理パターンとして“暗記必須”の書き方
'use strict';
{
const scores = [
70,
90,
80,
85,
];
scores.forEach((score, index) => {
console.log(`${index}: ${score}`);
});
}
☆forEach
とfor
の使い分け
-
forEach
が向いている場面(9割以上のケース)- 商品一覧や画像ギャラリーなど、全てのデータを順番に表示する
- ボタンなど複数要素に一括でイベントを追加する
- ユーザー一覧やエラーメッセージなどを順にHTMLへ反映
-
処理を途中で止める必要がないとき(break不可)
-
for
が向いている場面(条件による制御が必要なとき)- 特定の条件でループを途中で止めたい(
break
したい) - 1個飛ばし・偶数番目だけ処理などの細かい制御が必要
-
逆順でループしたい(後ろから処理したい)
- 特定の条件でループを途中で止めたい(
- 「配列を順に処理する」とは?
- Web制作の実務で例えるなら:
- 画像や商品などの一覧UIを自動で生成する
- 全ボタンや要素にまとめてイベントをつける
- APIから取得したデータをページ上に並べる
- 複数のエラー文を一括で表示する
- まとめると⇒
- ほとんどの場面では
forEach
でOK! - UIパーツを複数処理するときは
forEach
! - 特殊な処理をしたいときだけ
for
!
- ほとんどの場面では
☆ forEach
のインデックス(i)を使ったミニクイズ解説
const nums = [3, 2, 8];
nums.forEach((num, i) => {
console.log(i * 3);
});
-
num
:配列の中の値(3, 2, 8) -
i
:インデックス(何番目の要素か → 0, 1, 2)
- 実行時の内容
i(インデックス) | num(値) | i * 3 | 出力される値 |
---|---|---|---|
0 | 3 | 0 | 0 |
1 | 2 | 3 | 3 |
2 | 8 | 6 | 6 |
-
forEach((値, インデックス) => {...})
の形を覚えておくと便利! - 値だけ使うなら
(num) => {...}
- インデックスも使いたいときは
(num, i) => {...}
※第2引数がインデックス(i)なのはforEach()
など配列用のメソッドの時だけ。
- 配列は
[]
(角括弧)だったが、オブジェクトでは{}
(波括弧)を使う - 値に名前をつけるには、それぞれの値の前に文字列を配置して、
:
で区切る。const scores = {math : 80, english : 90};
→これがオブジェクトのリテラル表現で、これで1つの値になるので、定数に代入できる。math
やenglish
の部分を「キー」、80
や90
の部分を「値」、「キー」「値」を合わせて「プロパティ」と呼ぶ。
- プロパティへのアクセス、代入、追加、削除の方法
'use strict';
{
const scores = {
math: 80,
english: 90,
};
console.log(scores.english); //プロパティへのアクセス
scores.math = 88; //プロパティへの代入(ドットで繋げる)
scores.physics = 70; //プロパティの追加(新しく'物理学'を追加)
delete scores.english; //プロパティの削除('delete'を使う)
console.log(scores);
// 結果:90(scores.english)
// math: 88
// physics: 70
}
- プロパティが増えた場合、配列の反復処理を使いたいところだが、オブジェクトに対して直接
forEach()
を使うことはできない → 一旦オブジェクトを配列に変換してからforEach()
を使うというテクニックがある
'use strict';
{
const scores = {
math: 80,
english: 90,
};
let sum = 0; // 合計を保持するための変数をsumで宣言し、0で初期化
scores.physics = 70; // 物理学を追加
// 今までのやり方:1つずつ取り出して表示(数が少ないとき)
// console.log(scores.math);
// console.log(scores.english);
// プロパティ(キーと値)が増えたら毎回書くのが大変!
// → Object.entries()で [キー, 値] の配列に変換し、forEachでまとめて処理
const entries = Object.entries(scores);
entries.forEach((prop) => {
// 【復習:アロー関数】元々は
// entries.forEach(function(prop) { のところを短く見やすく。
// (prop)=プロパティ=['math', 80],['english', 90],
sum += prop[1]; // 値(点数)を1つずつ足す → 合計にする
console.log(`${prop[0]}: ${prop[1]}`); // math: 80、english: 90で表示
//prop[0] → 'math'(キー)、prop[1] → 80(値)
});
console.log(`Sum: ${sum}`);
console.log(`Average: ${sum / entries.length}`);
// 毎回、合計と平均を表示(平均=合計 ÷ 科目数)
// 結果:math: 80、english: 90、physics: 70、Sum: 240、Average: 80
// 合計(Sum)と平均(Average)は、ループの外でまとめて表示する
// → forEachの中に書いてしまうと「途中経過」が毎回表示されてしまう!
// ex)1回目の合計、2回目の合計、…と何度も表示されてしまうため、
// 「最終結果」を見たいときはループを抜けたあとに表示する。
}
- 配列の先頭や末尾の要素を操作するには
push
、pop
、unshift
、shift
があったが、途中の要素を挿入・削除する場合これらは使えず、代わりにsplice()
を使う。splice(➀変化する位置のインデックス[0][1][2].., ➁削除する要素の数, ➂追加する要素1, ➃追加する要素2...);
'use strict';
{
const scores = [70, 90, 80, 85];
scores.splice(2, 0, 77, 88);
// splice(➀変化する位置のインデックス[0][1][2]..,
// ↑ 上記の場合、[2]なら80の前を指す。
// ➁削除する要素の数(0), ➂追加する要素1(77), ➃追加する要素2(78)...);
// [70, 90, 77, 88, 80, 85];
const deleted = scores.splice(3, 1);
// [70, 90, 77, 88, 80, 85];から開始。
// splice(➀変化する位置のインデックス[3] = 88,
// ➁削除する要素の数(1));
// [70, 90, 77, 80, 85];
// 削除された[88]が delited に代入される。
scores.splice(2, 2, 30);
// [70, 90, 77, 80, 85];から開始。
// splice(➀変化する位置のインデックス[2] = 77,
// ➁削除する要素の数(2) → 77, 80を指す。
// ➂追加する要素1(30));
// // [70, 90, 30, 85];
console.log(scores); // [70, 90, 30, 85]
console.log(deleted); // [88]
}
-
要素を「|」で区切って表示させたい時は、
join('|')
を使う。 カンマで区切って表示させたい時は、引数なしのjoin()
(=デフォルトでカンマ区切りになる)を使う。 区切らずに繋げたい時は、join('')
と書く - 逆に、文字列を配列にしたい場合は
split()
を使う(例:split('|')
)
'use strict';
{
// const names = ['Taro', 'Jiro', 'Saburo'];
// 要素を|で区切って表示させたい時
// console.log(names.join('|')); // Taro|Jiro|Saburo
// 要素をカンマで区切って表示させたい時
// console.log(names.join()); // Taro,Jiro,Saburo
// |でもカンマでも区切らず繋げたい時
// console.log(names.join('')); // TaroJiroSaburo
// 逆に文字列から配列を作りたい時
const names = 'Taro|Jiro|Saburo';
console.log(names.split('|')); // ['Taro', 'Jiro', 'Saburo']
}
-
配列の中身を1つずつ加工して「新しい配列」を作りたい時は
map()
を使う(加工の例:税込価格に変換、名前に「さん」を付ける など)
'use strict';
{
const prices = [100, 150, 200];
// // それぞれを10%の税込み価格にした配列を別途用意する
// const pricesWithTax = [];
// prices.forEach((price) => {
// pricesWithTax.push(price * 1.1);
// }); //上記を簡潔に書くためにmap()を使う
const pricesWithTax = prices.map((price) => {
return price * 1.1;
});
console.log(pricesWithTax);
// ある配列の各要素を処理して新しい配列を作る場合、
// map() のほうが簡潔に書けるのでこちらを好む人も多い
}
-
配列から「条件に合う要素だけ」を取り出して新しい配列を作りたい時は
filter()
を使う(例:150円以上の商品だけ、偶数だけ、指定文字列を含む要素だけ、など)
'use strict';
{
const prices = [100, 150, 200];
// 150円以上の価格だけを抽出し、これとは別の新しい配列を作りたい時
// const pricesOver150 = [];
// prices.forEach((price) => {
// if (price >= 150) {
// pricesOver150.push(price);
// }
// }); //上記を簡潔に書くためにfilter()を使う
const pricesOver150 = prices.filter((price) => {
return price >= 150; // 条件に合う要素だけを新しい配列に入れる
});
console.log(pricesOver150); // [150, 200]
}
-
map
→ 元の配列の各要素を処理して同じ要素数の配列を返す -
filter
→ 元の配列の値はそのままに条件に合致するものだけを抽出する
- 分割代入:配列の代入を短く書ける
'use strict';
{
const scores = [70, 90, 80, 85];
// これらの値をこの後のコードでよく使うことになったので、
// それぞれに分かりやすい定数名を付けたくなったとする。
// const first = scores[0];
// const second = scores[1];
// const third = scores[2];
// const fourth = scores[3];
// ↑これを一気にスッキリ書ける!!
const [first, second, third, fourth] = scores;
// 新しく配列を作るコードではない。これは分割代入!混同注意!
console.log(first);
console.log(second);
console.log(third);
console.log(fourth);
}
- 分割代入の値の入れ替え
'use strict';
{
let start = 'Tokyo';
let goal = 'Osaka';
// 【値を入れ替える目的】
// ランキングの順位を入れ替える必要があるように、
// 「逆ルートで行きたい!」という希望からスタートとゴールを逆にする処理が必要になることもある。
// 表示順を変えたい時や、条件に応じて中身を交換したい時に使われる。
// 【今までのやり方】
// 一時変数 temp を使って、startとgoalの値を入れ替えていた
// let temp = '';
// temp = start;
// start = goal;
// goal = temp;
// 【分割代入を使った入れ替え】
[goal, start] = [start, goal];
// ↓ どういうこと?イメージ図
// [goal, start] = [ 'Tokyo', 'Osaka' ]
// ↑ ↑ ↑ ↑
// goal ← start start ← goal
// → つまり、お互いの値を同時に入れ替えている!
console.log(start); // 'Osaka'
console.log(goal); // 'Tokyo'
}
- スプレッド構文+レスト構文 ※rest=「残り」
-
レスト構文:受け取って1つにまとめる → "左辺"に使う。
const [first, ...others]
スプレッド構文:広げて展開する → "右辺"に使う。[70, 90, 80, 85, ...moreScores];
→ どちらも...
がポイント
'use strict';
{
const moreScores = [77, 88];
// 【スプレッド構文】配列の中身(要素)を "展開" して、別の配列に入れる
// → moreScoresの[77, 88]をscoresの末尾に展開して入れる
const scores = [70, 90, 80, 85, ...moreScores];
// 【分割代入】配列の最初の1つだけをfirstに、
// 残りすべてをothersという1つの配列にまとめて受け取る
// → 「残り全部まとめて」受け取る構文が【レスト構文】(...others)
const [first, ...others] = scores;
console.log(first); // 70(最初の1つだけ)
console.log(others); // [90, 80, 85, 77, 88](残りすべてをまとめて配列に)
- プリミティブ型(基本データ型) vs 配列(オブジェクト)代入時の挙動の違い:プリミティブ型 は「値そのもの」がコピーされるので、後から変更しても元の値には影響しない
- 一方、配列やオブジェクト は「参照(アドレス)」がコピーされるため、片方を変更するともう片方にも影響する。 しっかり複製したいときは、スプレッド構文などで「中身ごとコピー」する必要がある
'use strict';
{
// プリミティブ型
// let num = 10;
// const numBackup = num; // ← 中身「10」をそのままコピー
// num = 99;
// console.log(num); // 99(上書きしたから)
// console.log(numBackup); // 10(コピー元とは別物!)
// 上記処理を配列で行うと...
// const nums = [10, 20, 30];
// const numsBackup = nums; //← 中身じゃなく、「場所」をコピー
// nums[0] = 99;
// console.log(nums);// [99, 20, 30]
// console.log(numsBackup); // [99, 20, 30]。← あれ、変わっちゃってる!
// コピーしたいときはスプレッド構文!
const nums = [10, 20, 30];
const numsBackup = [...nums]; // ← これで中身をコピーできる!
nums[0] = 99;
console.log(nums); // [99, 20, 30]
console.log(numsBackup); // [10, 20, 30] ← ちゃんと別物!
}
- データ構造編まとめ(感想)
配列の表示方法の様々なパターンや代入のルールなど、コードを書く上で必須となる知識が詰まっていたセクションだった。しっかりと記憶に刻み込みたい。
JavaScript入門 DOM編(全22回)
- DOM(Document Object Model):画面の描画と連動し、コード編集を行ったら、その変更が即座に画面へ反映される仕組み
- 基本的にDOM操作では要素(p要素,section要素など)を取得した後にその要素を操作(テキストやスタイルを変更)するという構造になっている
-
querySelector('セレクタ')
を使うと、特定のHTML要素をJavaScriptから取得できる
例:'p'
,'.class名'
,'#id名'
などのCSSセレクタを指定 -
.addEventListener('click', () => {...})
を使うと、クリックなどのイベントに処理を追加できる
例:ボタンを押したら文字を変える、色を変える、非表示にする…等の動作を定義できる -
.textContent
プロパティを使うと、要素の中のテキストを変更できる
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
// console.log('Clicked');
document.querySelector('p').textContent = 'こんにちは';
// コンソール画面ではなく、pタグでちゃんと画面に文字列を表示させる
// OKボタンを押すと'Hello'が'こんにちは'に変わる処理が行われる
});
// querySelector('')の中に
// 取得したい要素を選択するための
// CSSセレクターを文字列で渡す
// addEventListener()の中に
// ➀'イベントの種類',
// ➁アロー関数でクリックした時に処理したい内容
// () => {処理内容};
// 2種類を入れる
// ここまで書くと、ボタンを押し、
// コンソールに「Clicked」と表示される仕組みが完成する
// console.log('Hello');
}
- ボタンをクリックしたらスタイルが変わる,スタイル操作用:
classList
- 要素のスタイルを変更するには、CSSでクラスを定義してから、
classList
を使う。クラス追加:classList.add
、クラス削除:classList.remove
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My JavaScript</title>
<style>
.pink-bg {
background: pink;
}
.red-border {
border: 2px solid red;
}
.green-color {
color: green;
}
</style>
</head>
<body>
<p class="green-color">Hello</p>
<button>OK</button>
<script src="./main.js"></script>
</body>
</html>
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
document.querySelector('p').classList.add('pink-bg', 'red-border');
document.querySelector('p').classList.remove('green-color');
// p要素を取得し、クラスを追加するためにclassList.addとする。
// ()内:p要素に追加したいクラス名 ※先頭の.(ドット)は要らない!!
// ,(カンマ)区切りで複数渡せる。
// 削除したいクラスはclassList.removeとする。
});
}
- クラスの切り替え(ON/OFF)には
classList.toggle
が便利 - 上記コードのように
.add()
/.remove()
を使うと、「追加」「削除」を明確に制御できる一方、同じクラスをON/OFFしたいだけならtoggle()
を使うと簡潔に書ける!
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
document.querySelector('p').classList.toggle('pink-bg');
// クラスがなければ追加、あれば削除(ON/OFFの切り替え)
});
}
- 補足:
toggle()
を使わずに if文で切り替えることもできる classList.contains('クラス名')
は、そのクラスを持っているかどうか判定するのに便利- ↓このように条件分岐で書けば、切り替え時の処理を柔軟にカスタマイズできる!
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
if (document.querySelector('p').classList.contains('pink-bg') === false) {
document.querySelector('p').classList.add('pink-bg');
} else {
document.querySelector('p').classList.remove('pink-bg');
}
// クラスの有無を確認し、自分で制御したいときはこのように書く
});
}
☆ classListの使い分け早見表
メソッド | 主な用途 | よくある使用シーン例 |
---|---|---|
classList.add() |
クラスを追加 | ✅ ボタンを押したら表示状態にする(例: モーダル表示) |
classList.remove() |
クラスを削除 | ✅ ボタンを押したら非表示にする(例: 閉じるボタン) |
classList.toggle() |
クラスのON/OFFを自動で切り替える | ✅ ボタン1つで表示/非表示を交互に切り替えたいときなど |
classList.contains() |
クラスを持っているかどうか確認する | ✅ 状況によって処理を分けたいとき(ON/OFFで挙動が違う場合) |
☆補足:どれを使えばよいか?
-
add/remove → 「ONにする or OFFにする」どちらか一方だけの操作を明示したいときに使う
(例:「開くときだけ処理したい」「消すだけのボタン」など) -
toggle → 状態を毎回切り替えるだけでOKなときに使う
(例:「開く⇔閉じる」を1ボタンでやりたいとき) -
contains → 今の状態を見て条件分岐させたいときに使う
(例:「開いている時だけこの処理をしたい」など)
- 複数の要素を取得するには、
document.querySelectorAll()
を使う - これで取得されるのは、「配列のように並んだリスト」 なので、1つずつ処理するために
forEach()
を使う必要がある -
.textContent
などをまとめて直接使おうとするとエラーになる(1つずつアクセスする必要がある)
<body>
<ul>
<li>Taro</li>
<li>Jiro</li>
<li>Saburo</li>
</ul>
<button>OK</button>
<script src="./main.js"></script>
</body>
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
document.querySelectorAll('li').forEach((li) => {
// 複数の要素を処理する場合(この場合はリストの3項目)、
// querySelectorAll()とforEach(反復処理)を組み合わせる必要がある
li.textContent = 'Changed!'; // Taro,Jiro,Saburoが同時に「Changed!」に
});
});
}
☆querySelector
vs querySelectorAll
使い分け早見表
※getElementById()
も存在するが、書き方としてもう古い上にforEach
が使えないので、今後は使わないほうがよい!!
メソッド | 主な用途 | 使い方の特徴 |
---|---|---|
querySelector() |
1つ目の要素だけ取得したいとき | CSSセレクタ形式で指定。1個だけ対象。 |
querySelectorAll() |
複数の要素すべてを取得したいとき |
forEach() などで1つずつ処理する。 |
- 特定の要素の取得
<body>
<ul>
<li class="target">Taro</li>
<li id="second">Jiro</li>
<li class="target">Saburo</li>
</ul>
<button>OK</button>
<script src="./main.js"></script>
</body>
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
document.querySelectorAll('.target').forEach((li) => {
// 特定のリストだけ取得したい場合、
// その項目のHTMLにクラス名(今回は.target)を付けて、
// js側をquerySelectorAll('.target')とする。
// ※予備で付けたID('#second')のみ動かすことも可。
li.textContent = 'Changed!';
});
});
}
◆ 特定の要素内の子要素を取得したい場合(親要素.querySelector)
- 通常は
document.querySelector('.class')
などで要素を取得するが、
複数の同名クラスがある場合や、今開いているモーダルの中だけを対象にしたい場合などは…
const modal = document.querySelector('#popup'); // 親要素を取得
const content = modal.querySelector('.popup-content');
// modalの中の特定要素を取得
☆HTMLを編集しなくても、jsのDOM操作だけで要素を追加できる
-
createElement('li')
:新しいli
タグを生成する -
appendChild()
:親要素の末尾(1番最後) に追加する -
insertBefore()
:親要素内の指定した要素の前に追加する -
confirm()
:ユーザーに確認ダイアログを表示
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
// 新しい <li> 要素を作成する
const liElement = document.createElement('li');
// テキストを設定
liElement.textContent = 'Hanako';
// ▼ 要素の追加パターン①:末尾に追加(appendChild)
// 実行結果:Taro → Jiro → Saburo → Hanako(←いちばん最後に追加される)
// document.querySelector('ul').appendChild(liElement);
// ▼ 要素の追加パターン②:指定した要素の「直前」に追加(insertBefore)
// 実行結果:Taro → Hanako → Jiro → Saburo(←Jiroの前に割り込む)
// 第1引数:追加したい要素(liElement)
// 第2引数:目印となる要素(ここでは「Jiro」)
// document.querySelector('ul').insertBefore(
// liElement,
// document.querySelector('#second')
// );
// ▼ 要素の削除(確認ダイアログでOKを押すと「Jiro」が消える)
if (confirm('Sure?') === true) {
document.querySelector('#second').remove();
}
});
}
- フォーム部品の値は
.value
で取得,変更ができる(例:テキストフォームの内容やドロップダウンリストなど)
<body>
<!-- <input type="text"> -->
<textarea></textarea>
<button>OK</button>
<script src="./main.js"></script>
</body>
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
// alert(document.querySelector('input').value);
// ↑ textContentではない!!
// フォーム部品の値にアクセスするにはvalueを使う!!
// 結果:OKボタンを押すと、
// テキストフォームに入力した内容のアラートが出せる。
document.querySelector('textarea').value = '';
// ''の部分=空文字を代入→
// ボタンをクリックしたらtextareaの中の値がクリアされる
});
}
- フォーム部品の値 取得:注意➀!テキストフォームの値やドロップダウンリストは
querySelector
で取得できるが、ラジオボタンの場合はquerySelectorAll
+forEach
を使う必要がある!!(また、HTMLのinput
タグ内はname
属性が要る!!) - 理由:ラジオボタンは、見た目は1つの選択肢に見えても、HTML上は個別の
<input>
タグの集合体。そのため、「複数ある中からどれが選ばれているか」を調べるには、1つずつchecked
状態を判定する必要がある!
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
// すべてのラジオボタンを取得してループ処理
document.querySelectorAll('input').forEach((radio) => {
// checked === true なら選択されているラジオボタン
if (radio.checked === true) {
alert(radio.value); // 選ばれた値をアラートで表示
}
});
});
}
- フォーム部品の値 取得:注意②!チェックボックスの場合も
querySelectorAll
+forEach
を使う必要があるが、さらにここでは配列処理,push
やjoin
も組み合わせる! - 理由:チェックされた項目の数だけアラートが表示されてしまうため!!
- +選ばれた値を一括で表示するには
push()
で配列にためて、join()
で文字列にまとめるのがベストなため
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
const colors = []; // 複数の値を保持するために配列を使う
// 空の配列で初期化する
document.querySelectorAll('input').forEach((checkbox) => {
if (checkbox.checked === true) {
colors.push(checkbox.value);
}
});
alert(colors.join(','));
// 反復処理が終わったら、アラートで colors の値を文字列にして表示
// 色の名前の間にカンマ(チェックした色 → red,green と出る。)
});
}
☆実用的な例!input
イベントで、入力した文字を文字数カウント
- これまでの例では「ボタンを押したとき」に
.value
を取得していたが、input
イベントでは入力のたびにリアルタイムで値の変化を監視できる - つまり、「値をいつ取得するか」の違いがあり、
input
は即時反応が必要な場面(例:文字数カウントやバリデーション) に最適
<body>
<input type="text">
<p></p>
<script src="main.js"></script>
</body>
'use strict';
{
// 入力欄に文字が入力されるたびにイベントが発火
// ▼ イベントの使い分け:'input'は text や textarea に使うイベント
// 'change'はラジオやチェックボックス用
document.querySelector('input').addEventListener('input', () => {
const pElement = document.querySelector('p'); // 結果を表示するpタグ
const inputElement = document.querySelector('input'); // 入力されたテキスト
// 入力文字そのものを表示する場合はこちら
// pElement.textContent = inputElement.value;
// 文字数だけを表示する
pElement.textContent = inputElement.value.length;
});
}
}
☆補足:イベントの使い分け(input / change):input
イベントとchange
イベントはどちらもフォーム入力の変化に反応するが、使い分けが必要
-
input
イベント:文字入力中にもリアルタイムで反応(例:テキストやテキストエリア) -
change
イベント:選択が確定されたときに反応(例:ラジオやチェックボックス)
フォーム部品 | イベントタイプ | 説明(いつ反応するか) |
---|---|---|
テキスト入力欄 | input |
入力するたびに即時反応 |
テキストエリア(複数行入力) | input |
入力するたびに即時反応 |
プルダウンリスト |
input /change
|
選択肢を変更した瞬間(input でもOK) |
ラジオボタン | change |
選択肢をクリックして変更した時のみ |
チェックボックス | change |
チェック/解除をした瞬間 |
- (テキストフォームに)フォーカスを当てた時の動作用:
focus
イベント - フォーカスを外した時の動作用:
blur
(ブラー)イベント -
ページを読み込んだと同時にフォーカスを当てておく動作用:
focus()
イベント
※用途によって記述位置が異なるので注意!!
'use strict';
{
document.querySelector('input').addEventListener('focus', () => {
// フォーカスを当てた時の動作
document.querySelector('p').textContent = 'English only!'; // メッセージ表示
});
document.querySelector('input').addEventListener('blur', () => {
// フォーカスを外した時の動作
document.querySelector('p').textContent = ''; // テキストフォームの文字を消す
});
// ページを読み込んだと同時にフォーカスを当てておく動作
document.querySelector('input').focus();
}
☆keydown
イベント:どのキーが押されたか取得する
- 文章全体に対してイベントリスナーを設定したい場合、
document
に対して直接addEventListener
する - 引数名は何でもよいが、一般的にイベントの
e
が使われる -
e.key
は、キーボードで押されたキーの“名前(文字列)”を取得できるプロパティで、 例えば「a」キーを押すと"a"
、「Enter」キーを押すと"Enter"
という文字列が返される。この値をp
要素のtextContent
に代入すれば、ユーザーが押したキーの内容をそのまま画面に表示することができる - この仕組みは、左右キーでスライドを切り替える画像ギャラリーや、Enterキーでフォームを送信する機能、W/A/S/Dキーでキャラクターを動かすゲームなど画面を動かすUIに応用できる
<body>
<p></p>
<script src="main.js"></script>
</body>
'use strict';
{
document.addEventListener('keydown', (e) => {
document.querySelector('p').textContent = e.key;
});
}
☆mousemove
イベント:マウスの動きを追って、X座標/Y座標の位置を表示
e.clientX
/e.clientY
:マウスカーソルが、ブラウザの表示領域(ビューポート)の中でどこにあるかを「X座標(横)」「Y座標(縦)」として数値で返すプロパティ- お絵かきツールや、マウスで動かすゲームを作れる
プロパティ | 何の値? | 単位 | 例 |
---|---|---|---|
e.clientX |
ブラウザ左上からの横方向の位置 | px |
X: 100 → 左から100pxの位置 |
e.clientY |
ブラウザ上端からの縦方向の位置 | px |
Y: 200 → 上から200pxの位置 |
'use strict';
{
document.addEventListener('mousemove', (e) => {
document.querySelector('p').textContent = `X: ${e.clientX} Y: ${e.clientY}`;
// ':'(コロン)があるのでテンプレートリテラルを使う
});
}
☆submit
イベント:HTML
に記入したform
タグ内のフォームに入力した文章をフォームの下に表示できる。ただし、再読み込みによるフォームデータ削除を防ぐため、submit
イベントを使う際は必ずe.preventDefault();
(ブラウザの“標準の動作”をキャンセル)を記述すること
→ ユーザーにとって自然な操作(Enter送信など)を活かしたいなら、form
+submit
+preventDefault
が基本パターン!
処理したいこと | どうする? |
---|---|
入力内容を送信せず画面に表示 |
submit イベント+preventDefault()
|
Enterキーで送信させたい |
<form> タグを使う |
<body>
<form>
<input type="text">
<button>OK</button>
</form>
<p></p>
<script src="main.js"></script>
</body>
'use strict';
{
document.querySelector('form').addEventListener('submit', (e) => {
// ページの再読み込みを防ぐ
e.preventDefault();
// 入力欄の値をpタグに表示
document.querySelector('p').textContent = document.querySelector('input').value;
// 【復習】.value:inputタグの中に入力された値(文字列)を取得するためのプロパティ
});
}
重要トピック➀:「属性操作」
🔰「属性操作」とは?
- HTML要素には、
src
やhref
のような「属性」がある。これらの値を JavaScriptから直接書き換えることで、表示や挙動を動的に変更できる
✏️ 基本ルール
- 属性操作の基本は、
要素.属性名 = 値;
<img src="dog.png">
<a href="https://dotinstall.com">リンク</a>
// src属性の書き換え(画像切り替え)
document.querySelector('img').src = 'cat.png';
// href属性の書き換え(リンク先の変更)
document.querySelector('a').href = 'https://256times.com';
- HTMLで使われている属性名(src、hrefなど)と同じ名前のプロパティを使うのが基本!
⚠️ 注意が必要な「3つの属性」
①class
属性は .class
ではなく .className
を使う
-
class
は JavaScript の予約語のため、プロパティ名としては使えない
// ↓これは構文エラーになる
// element.class = 'red-border';
// 正しくは className を使う
element.className = 'red-border'; // ※既存のクラスを上書きしてしまう
- 既に他のクラス(例:pink-bg)が付いている場合は、空白で区切って全て書く必要あり
element.className = 'pink-bg red-border';
- ✅推奨:classList.add() を使う!
element.classList.add('red-border');
- 既存のクラスにそのまま追加される
-
.remove()
や.toggle()
も使える - 今までの学習で使っていた方法。基本はこちらを使えばOK
②disabled
/checked
/selected
はtrue
/false
で制御する
- これらは「ある/なし」で状態を表す属性
<button disabled>OK</button>
- ↑これを JS で制御するには:
const btn = document.querySelector('button');
btn.disabled = true; // ボタンを無効にする
btn.disabled = false; // ボタンを有効にする
- 同様に:
checkbox.checked = true; // チェックをつける
option.selected = true; // セレクトで選ばれた状態にする
- ✅ これらは 「状態属性」 と呼ばれ、
true
/false
で操作するのが基本ルール。
③checked
の補足:以前は「取得」だけを使っていた
// チェックされているラジオボタンを取得
if (radio.checked === true) {
alert(radio.value);
}
- → ここでは
.checked
は状態の確認(読み取り)として使っていた - →
.checked = true
のように状態の操作(書き込み) として使う方法が登場
→.checked
は「取得と設定の両方ができる」プロパティ!
☆まとめ:属性操作の基本早見表
属性 | 操作例 | 説明 |
---|---|---|
src |
img.src = 'cat.png' |
画像の切り替え |
href |
a.href = 'https://〜' |
リンク先の変更 |
className |
p.className = 'a b' |
クラス名を一括で上書き |
classList |
p.classList.add('c') |
クラスを追加(推奨) |
disabled |
btn.disabled = true / false |
ボタンの有効/無効を切り替え |
checked |
checkbox.checked = true |
チェックを付ける |
selected |
option.selected = true |
セレクトボックスの選択状態にする |
☆補足:この章で新たに出てきたキーワード
キーワード | 出現タイミング | 覚え方/メモ |
---|---|---|
.className |
初登場(class属性操作) | 基本は classList 、上書き時のみ使用 |
.disabled |
初登場 | 状態属性 → true/false で切り替え |
.selected |
初登場 | セレクトボックスで選択状態を設定 |
.checked |
既出(取得)→今回で「設定」も可 | 状態の取得/設定どちらもできる |
重要トピック②:「style属性」
-
element.style
は、HTMLのstyle属性を直接操作できるプロパティ - CSSと異なり、プロパティ名はキャメルケース(小文字始まり+単語区切りは大文字) で書く。※例:
font-size
→fontSize
、background-color
→backgroundColor
- ⚠️ element.style = 'font-size: 24px' のような書き方はエラーにはならないが無効
→ styleは「オブジェクト」なので、個別のプロパティに代入する形でしか使えない -
style.fontSize = '24px'
のように、文字列として値(単位付き)を代入すればOK。※動的にスタイルを変更したい場面(文字サイズの調整・背景色の切り替えなど)で使う
☆style変更の代表例(対応表)
CSSでの記述 | JSでの書き方 |
---|---|
color: red; |
element.style.color = 'red' |
font-size: 24px; |
element.style.fontSize = '24px' |
background-color: #ccc; |
element.style.backgroundColor = '#ccc' |
border-radius: 8px; |
element.style.borderRadius = '8px' |
overflow: hidden; |
element.style.overflow = 'hidden' |
<body>
<p style="color: red;">Hello</p>
<button>OK</button>
<script src="main.js"></script>
</body>
'use strict';
{
document.querySelector('button').addEventListener('click', () => {
// document.querySelector('p').style = 'font-size: 24px';
document.querySelector('p').style.fontSize = '24px';
});
}
- DOM編まとめ(感想)
ここまでで最も実用的で、パターンごとに様々な書き方があるなあ、後から見返して使えたら嬉しいな、とのんびりまとめていたら、最後で新ルールが怒涛の勢いで登場して混乱。細かいところは継続して詰めるとして、これで何とか、JavaScript基礎習得セットになっただろうか。学ぶ意欲を絶やさず、このままどんどん深く知識の沼に潜っていきたい。
JavaScript入門 文字列操作編(全7回)
-
string[0]
:文字列の「n文字目」(ここでは1文字目)を取得する記法 (※文字列のため、string[0] = 'w'のような書き換えはできない)
☆文字列判定 - 入力ゆれ への対処
-
toUpperCase()
/toLowerCase()
✔「文字列をすべて大文字/小文字に変換」するメソッド。
✔変換してから比較することで、大文字小文字を区別せずに判定できる。
例:"Taro"も"tArO"も、toUpperCase()
すれば"TARO"になる。
-
.trim()
✔前後の空白文字(全角・半角問わず)を除去。
入力時にうっかり打ってしまったスペース対策に有効。
string.trim()
を先にかけることで、不要な誤判定を防げる。例:
string.toUpperCase().trim() === 'TARO'
→入力" Taro "
のような文字列が来ても
→.trim()
→"Taro"
(前後の空白除去)
→.toUpperCase()
→"TARO"
(大文字に統一)
"TARO" === 'TARO'
→ 一致!
-
.includes()
:特定の文字列が「どこかに含まれているか」を判定 -
.startsWith()
:特定の文字列で「始まっているか」を判定(先頭限定) -
.indexOf()
:特定の文字列が「何文字目にあるか」を数値で取得(0なら先頭)
if (email.includes('taro')) { // "taro"が含まれてるか(Anywhere)
if (email.startsWith('taro')) { // "taro"で始まってるか(分かりやすい)
if (email.indexOf('taro') === 0) { // "taro"で始まってるか(位置で判定)
- →
includes()
:あればOK、startsWith()
:最初にあるならOK - 迷ったら
.includes()
(汎用的・よく使う) - 先頭に限定したいなら
.startsWith()
(可読性◎)
-
.slice(start, end)
:文字列や配列から「start番目からend直前まで」を取り出す
const loc = email.indexOf('@'); // '@'を変数に代入して,@の直前を切り出す
console.log(email.slice(0, loc));
-
.replace('A', 'B')
:最初に見つかった'A'を'B'に置換する
const emails = [
'taro@example.com',
'kintaro@example.com',
'kojiro@example.com',
];
emails.forEach((email) => {
console.log(email.replace('@example.com', ''));
// '@example.com'を''(空文字)に置換=@以降を削除
// ='taro''kintaro''kojiro'
});
-
.split('文字')
:指定した文字で区切って配列に変換する
emails.forEach((email) => {
const items = email.split('@');
console.log(items[0]);
// "@"を境界線として文字列を2つに分ける
// "taro@example.com" → ["taro", "example.com"]
// そのうちの items[0](=最初の要素)を表示
// → forEachでループしているため全要素表示
// ='taro''kintaro''kojiro'
});
-
.repeat(n)
:指定回数だけ文字列を繰り返す"*".repeat(3)
→***
-
.padStart(n, 'X')
:指定文字数(n)になるよう右揃え - ⇔左揃え:
.padEnd(n, 'X')
const counts = [6, 12, 8, 15];
counts.forEach((count) => {
const bar = '*'.repeat(count);
// * を連結しグラフにする(配列内の数だけ * が出力される)
const label = String(count).padStart(2, ' ');
console.log(`${label}: ${bar}`);
// label部分の桁を揃えるために、
// pddStartを使って数字を右揃えする
// (揃えたい文字数, 文字詰めのための空白)
// 6: ******
// 12: ************
// 8: ********
// 15: ***************
});
☆文字列操作メソッドの選び方ガイド
目的 | メソッド | 簡単な例 | 補足 |
---|---|---|---|
特定の文字列が含まれているか調べたい | .includes('abc') |
email.includes('taro') |
どこかに含まれていれば true |
特定の文字列で始まっているか調べたい | .startsWith('abc') |
email.startsWith('taro') |
先頭限定の判定 |
特定の文字列の位置(何文字目か)を知りたい | .indexOf('abc') |
email.indexOf('@') |
先頭なら 0、なければ -1 |
一部だけを取り出したい(部分切り出し) | .slice(start, end) |
email.slice(0, 4) |
endの直前まで切り出す(破壊しない) |
一部を他の文字列に置き換えたい | .replace('A', 'B') |
email.replace('@x', '') |
最初の1つだけ置換(全部置換は/A/g ) |
特定の文字で区切って配列に変換したい | .split('文字') |
email.split('@') → ['taro', 'example.com']
|
配列の形で使いたいとき便利 |
すべてを大文字にしたい | .toUpperCase() |
name.toUpperCase() |
入力チェックや表記統一に使える |
すべてを小文字にしたい | .toLowerCase() |
name.toLowerCase() |
同上(大文字とセットで使われがち) |
前後の空白を削除したい(うっかり入力対策) | .trim() |
input.trim() |
ユーザー入力時に定番 |
特定の文字を繰り返したい(グラフや装飾) | .repeat(n) |
"*".repeat(3) → "***"
|
同じ文字を指定回数だけ繰り返す |
文字数を揃えて右揃えしたい(数字の整列) | .padStart(n, ' ') |
"5".padStart(2, ' ') → " 5"
|
指定文字数になるよう右揃え |
文字数を揃えて左揃えしたい(表の整形) | .padEnd(n, ' ') |
"5".padEnd(2, ' ') → "5 "
|
指定文字数になるよう左揃え |
JavaScript入門 fetch API編(全8回)
- バックグラウンドで実行させる処理「非同期処理」
- 現在の画面を保持しつつ外部のサイトと通信する→ユーザー体験を改善
- 命令は
fetch()
が基本で、取得する情報は「JSON(JavaScriptで配列やオブジェクトを表現するための記法で書かれたデータ形式)」で用意されている
- 非同期処理が終わったあとの値を取得するには、functionの前に
async
、fetchの前にawait
を付ける!(※awaitは必ずasync関数内で使う。)
⇒結果、responseというオブジェクトが出力されるので、JSON形式のデータを取り出すには、const users = await response.json();
を書く。 -
try {...} catch (err) {...}
:エラーメッセージを表示して、ユーザーに知らせる。'Error log: ' + err
で開発中用のエラー出力を行う - 関数式の場合のasyncの位置は()の前のため、使用時は注意
- React、Next.js、VueなどのモダンJSで書く場合はアロー関数式が主流
-
async
await
の旧スタイルとして、.then()
と.catch()
を使い、非同期処理の結果とエラーを順番に処理する方法もあり、これが使用されているコードもあるため、頭の片隅に入れておく
// 関数宣言バージョン
function showHeader () {
console.log('Header');
}
async function showUsers() {
try {
// fetchのリンク先データに""を忘れている等、
// 予期せぬエラーに備えてtry{} catch(err) {}を使う。
// ⇒エラーメッセージを表示して、ユーザーに知らせる。
const response = await fetch('https://dotinstall.github.io/setup/fetchapi/users.json');
const users = await response.json();
console.log(users);
} catch (err) { // err=適当な引数名
console.log('Something went wrong getting user data');
console.log('Error log: ' + err); // 開発中用のエラー出力
}
function showFooter () {
console.log('Footer');
}
// 関数の実行
showHeader();
showUsers();
showFooter();
}
// アロー関数式バージョン
// 関数宣言A(巻き上げ可能)
function showHeader () {
console.log('Header');
}
// 関数式B(巻き上げ不可)
const showUsers = async() => {
// 関数式の場合のasyncの位置は()の前。
// 関数宣言を使うか関数式を使うかは、
// プロジェクトの方針や自身の好みで決めればいいが、
// 関数式で書く場合、asyncの位置を間違いやすいので注意
try {
const response = await fetch('https://dotinstall.github.io/setup/fetchapi/users.json');
const users = await response.json();
console.log(users);
} catch (err) {
console.log('Something went wrong getting user data');
console.log('Error log: ' + err);
}
};
// 関数宣言C(巻き上げ可能)
function showFooter () {
console.log('Footer');
}
showHeader(); // 関数呼び出しA
showUsers(); // 関数呼び出しB
showFooter(); // 関数呼び出しC
JavaScript入門 データ型編(全9回)
- データ型:数値や文字列、配列(例:
[70, 80, 90]
) やオブジェクト(例:{math: 80, english: 90}
) とはまた異なるデータ型、真偽値(true
false
)、undefined
null
にスポットを当てる - 真偽値(
true
false
):オンかオフか等、2択のデータ表現によく使われる -
!=
:否定の論理演算子。↓以下、それを使用したクリックイベント
<p>Dev Mode is On.</p>
<button>Switch</button>
<!-- Switch というボタンをクリックし、
開発者モードのon/offを切り替える -->
'use strict';
{
let isDevMode = true;
const pElement = document.querySelector('p');
const buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', () => {
if (confirm('Are you sure?')) { // 確認用ダイアログ表示
if (isDevMode === true) { // ←ここで`=== true`を消しても動く
pElement.textContent = 'Dev Mode is Off.';
// ➀クリックしたらOff表示に!
// isDevMode = !isDevMode;(クリックで真偽値反転!)
// (➀➁同じなので条件分岐の外に出しても同じ意味)
} else {
pElement.textContent = 'Dev Mode is On.';
// ➁クリックしたらOn表示に!
// isDevMode = !isDevMode;(クリックで真偽値反転!)
// (➀➁同じなので条件分岐の外に出しても同じ意味)
}
isDevMode = !isDevMode; // 条件分岐の外に出したもの
});
}
}
// 早期リターン導入バージョン
'use strict';
{
let isDevMode = true;
const pElement = document.querySelector('p');
const buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', () => {
if (!confirm('Are you sure?')) {
return;
}
// ifの中にifがあると見づらい!という時に:早期リターン
// false⇒何もしない/true⇒次の処理に移る
// ダイアログ用 条件分岐内の処理を外に出す
if (isDevMode) {
pElement.textContent = 'Dev Mode is Off.';
} else {
pElement.textContent = 'Dev Mode is On.';
}
isDevMode = !isDevMode;
});
}
-
Truthy(真扱い)と Falsy(偽扱い)の値がある
→ 例:空文字''
や0
、null
、undefined
、NaN
は 偽扱い
→ それ以外(空配列[]
、空オブジェクト{}
、文字列など)は 真扱い -
if (name !== '')
は冗長 →if (name)
と書けば、name
が Truthy なら実行、Falsy ならelse
に進む
const name = prompt('Your name?');
// if (name !== '') {
// 空文字の式を書かなくても動く
if (name) {
console.log(`Hi, ${name}`);
} else {
console.log('Hi, nobody!');
}
-
undefined
の活用:値の定義チェック
// undefinedが出るコード
// (関数にreturnを書かなかった時の返り値以外の例)
// let x;
// console.log(x); 宣言されただけの変数の値
// const scores = [70, 90, 80];
// console.log(scores[100]);
// 配列で存在しないインデックスの値にアクセスした時
const score = {math: 80, english: 90};
// console.log(score.history);
// オブジェクトで存在しないプロパティにアクセスした時
if (score.history === undefined) {
console.log('History score not defined!');
}
// undefinedは値が定義されているかどうかのチェックによく使われる
-
null
の活用:値の仮割り当て( 集計中 の状況など)
// null
// 最初からどんな科目があるか明示したいが、
// 値が確定でない場合にnullを割り当てる
const score = {
math: 80,
english: 90,
physics: null,
};
if (score.physics === null) {
console.log('Physics score is null!');
}
-
typeof
:データ型が調べられる
console.log(typeof 5); // number
console.log(typeof 'Hello'); // string
console.log(typeof [5, 2]); // object
console.log(typeof {math: 80, english: 90}); // object
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object←ここだけ注意!
JavaScript入門 日時操作編(全8回)
- 現在の日時を取得:
new Date()
+toLocaleString()
( yyyy/mm/dd XX:XX:XX 表示) -
getFullYear()
:年を取得(fullを忘れずに) -
getMonth()
=月を取得:0(1月), 1(2月), 2(3月)… -
getDay()
=曜日を取得:0(日曜), 1(月曜), 2(火曜)… - データを0から数える点が最も間違えやすいため要注意
const d = new Date(2000, 2, 0);
- →うるう年の日付に0を指定することでうるう日を取得できる。
- 結果:
2000/2/29 00:00:00
const d = new Date();
// new Date() = 現在の日時に関するデータをくれる
console.log(d); // 現在の日時表示(Wed Sun 21...出力が長くややこしい)
console.log(d.toLocaleString());
// toLocaleString()⇒スッキリとした表示にしてくれる yyyy/mm/dd XX:XX:XX
console.log(d.getFullYear()); // 年を取得(fullを忘れずに)
console.log(d.getMonth()); // 月を取得。0(1月), 1(2月), 2(3月), ...
// 例:const d = new Date(2000, 3, 11);
// console.log(d.toLocaleString());
// → 2000年4月11日を表示。結果:2000/4/11 00:00:00
// 裏技。const d = new Date(2000, 2, 0);
// うるう年の日付に0を指定でうるう日を取得。2000/2/29 00:00:00
console.log(d.getDate()); // 日付を取得
console.log(d.getHours()); // 時間を取得(複数形,sを忘れずに)
console.log(d.getMinutes()); // 分を取得(複数形,sを忘れずに)
console.log(d.getSeconds()); // 秒を取得(複数形,sを忘れずに)
console.log(d.getMilliseconds()); // ミリ秒を取得(複数形,sを忘れずに)
console.log(d.getDay()); // 曜日を取得。0(日曜), 1(月曜), 2(火曜), ...
- 日付の更新:
setDate()
(⁺
を付けるとXX日後を取得)
const d = new Date(2000, 3, 11);
// d.setDate(23);
d.setDate(d.getDate() + 100);
// +100で100日後を取得
console.log(d.toLocaleString());
- 更新前の日付のバックアップを取る:
getTime()
(本来'経過ミリ秒'の表示に使われる)
const d = new Date(2000, 3, 11);
const dBackup = new Date(d.getTime());
d.setDate(23);
console.log(d.toLocaleString()); // 23
console.log(dBackup.toLocaleString()); // 11
-
Math.floor()
:時間同士の差を求める (詳細は割愛。必要な時に再度調べる)
UIパーツ実装メモ
☆HTMLに存在しないクラス名がCSSやJavaScriptにいきなり出てくる現象
- 状態変化クラスとは?: UIの「状態」を切り替えるためだけに使うクラス
const overlay = document.querySelector('.overlay');
overlay.classList.add('show'); // ← showは最初は書かれてない
📌よくある状態変化クラスの例
クラス名(通常) | BEM風の例 | 意味・使いみち |
---|---|---|
.show / .open
|
.modal--open / .menu--open
|
表示状態(モーダルやメニューが開いている) |
.hide / .close
|
.modal--hidden |
非表示状態(あまり使わない。現場では .hidden が一般的) |
.active |
.tab--active / .slide--active
|
現在アクティブな要素(タブ・スライド・ボタンなど) |
.disabled |
.button--disabled |
使用不可・グレーアウト状態 |
.appear |
.accordion--appear |
アコーディオンなどで「開いた状態」。「答えを表示している」などの明示的な状態 |
☆【復習:CSS】 細部で魅せるUI:メニューをふわっと順番に表示
- GSAPなどのJSライブラリを使うとさらに細かい制御もできる
プロパティ / セレクタ | 内容・役割 |
---|---|
opacity: 0 / 1 |
最初は透明→表示 |
transform: translateY(16px) |
下から上にふわっと表示 |
transition |
アニメーションの速度設定 |
.overlay.show li |
モーダル表示時に li をアニメーション開始 |
.nth-child(n) |
順番ごとにアニメーションを 遅延して再生 |
transition-delay: .1s ~ |
それぞれ .1s , .2s , .3s …で「順番感」を演出 |
☆parentNode
とは?(アコーディオンメニューに登場)
- その要素の“ひとつ上の親”を取得するプロパティ
- ⇒なぜ
parentNode
を使うのか?⇒アコーディオンUIにて、<dt>
をクリックしたときに親の<div>
にクラスをつけるため
<div>
<dt>質問</dt> ← これが対象だとしたら
<dd>回答</dd>
</div>
const dts = document.querySelectorAll('dt');
dts.forEach(dt => {
dt.addEventListener('click', () => {
dt.parentNode.classList.toggle('appear');
// この .appear クラスが付いたら、
// その中の <dd> を display: block で表示させる
// =クリックされた質問(<dt>)の答え(<dd>)を表示・非表示に切り替える
});
});
☆【解決済み】モーダルウィンドウが開かないエラーの解決方法メモ
-
発生した問題:クリックでモーダルを表示したいのに、なぜかURLに「#popup」をつけたときしか表示されない
-
原因:CSSとJavaScriptの表示制御が食い違っていた
- CSSでは
:target
(=URLの#)で表示 - JSでは
.hidden
クラスを使って表示切り替え
→ 別々の仕組みが競合し、うまく動いていなかった
- CSSでは
-
解決方法:
-
.popup:target { ... }
を削除し、CSSの表示切替は.hidden
で統一 -
.popup
にopacity: 1
/.hidden
にopacity: 0
を設定 -
:target
=「HTML+CSSで完結する手法」と、.classList.add()
=「JavaScriptで動的に操作する手法」はどちらかに統一しないと競合するため、混ぜないよう注意
-
.popup {
opacity: 1;
pointer-events: auto;
/* 上記2行.popup:target内に入れておりエラー,
.popup:target{ ...}を削除,
こちらに移動させてエラー解消済み。 */
}
.hidden {
opacity: 0;
pointer-events: none;
/* 上記2行.popup内に入れておりエラー,
display: none; を削除,
こちらに移動させてエラー解消済み。 */
}
<div id="popup" class="popup hidden">
<div class="popup-overlay"></div>
<!-- ポップアップ外のクリックを検知 -->
<div class="popup-content">
<!-- 画像 -->
<img src="https://placehold.jp/300x500.png" alt="#" class="popup-image">
<!-- 画像の説明 -->
<p class="works__caption-title">テスト<br>(テスト/テスト)</p>
<!-- 閉じるボタン -->
<button type="button" class="close-btn">閉じる</button>
</div>
</div
'use strict';
{
const openBtns = document.querySelectorAll('.works__thumbnail-link');
openBtns.forEach(open => {
const modal = open.closest('.works__item').querySelector('.popup');
// document.querySelector → open.closest('.works__item')
// 修正後:クリックされたボタンに対応するモーダルを取得
// + open定義後に書く必要があるためアロー関数内に入れる
// .closest('...'):クリックした要素から親をたどって特定クラスを持つ要素を探す
const mask = modal.querySelector('.popup-overlay');
const close = modal.querySelector('.close-btn');
// 上記2行:document.querySelector → modal.querySelector
// 「特定の要素の中だけ」で探す
open.addEventListener ('click', (e) => {
e.preventDefault();
modal.classList.remove('hidden');
mask.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// モーダルを開いたときに背景スクロールを止める
// ※body(背景全体)にoverflow: hiddenを指定
modal.querySelector('.popup-content').scrollTop = 0;
// ポップアップを開いた時に 内容のスクロール位置をリセット
});
close.addEventListener ('click', () => {
modal.classList.add('hidden');
mask.classList.add('hidden');
document.body.style.overflow = '';
// モーダルを閉じたときにスクロールを元に戻す
// ※空文字 '' で元のスタイルに戻る
});
mask.addEventListener('click', () => {
close.click();
});
});
// スコープの概念に基づき、modal / close / mask.addEventListenerを
// forEachの「中」に入れる。
}
☆カルーセルの実装で登場した新知識
用語・処理名 | 意味・役割 |
---|---|
.children |
要素の子ノード(要素ノードだけ)を 配列っぽく返す(HTMLCollection) |
getBoundingClientRect() |
要素の 実際の表示サイズや位置 を取得(レスポンシブ対応に強い!) |
.length - 1 |
最後の要素の インデックス番号 を取得(配列の先頭が 0 番なので) |
[0] |
配列(やコレクション)の 最初の要素 を取得する記法 |
.style.transform |
要素の transform を JavaScriptで動的に変更 できる |
translateX() |
要素を X軸方向(横方向)にスライドさせるための CSS 関数 |
updateButtons() |
自作関数(ユーザー定義関数) 「前へ」「次へ」ボタンの表示/非表示を制御 スライド移動のたびに呼び出して UIの状態を更新 |
moveSlides() |
自作関数 スライドを現在のインデックスに応じて横に移動させる |
setupDots() |
自作関数 画像の数だけ 丸ボタンを動的に生成・初期状態を設定 |
updateDots() |
自作関数 アクティブな丸ボタン(currentクラス)を更新 |
function 関数名() { ... } |
ユーザー定義関数の書き方(function宣言) 巻き上げ(hoisting)されるため、スクリプト内のどこからでも呼び出し可能 |
'use strict';
{
const next = document.querySelector('.next');
// 「次へ」ボタンを取得
const prev = document.querySelector('.prev');
// 「前へ」ボタンを取得
const ul = document.querySelector('ul');
// スライド全体(ul)を取得
const slides = ul.children;
// ul内のli要素たちを配列っぽく取得(HTMLCollection)
const dots = [];
// ボタンを格納するための配列を宣言(pushされたものはここへ)
// ※丸いボタンを動的に生成する準備
let currentIndex = 0;
// 現在表示中のスライドのインデックス(最初は0)
// 0番目(=1枚目のスライド)
function updateButtons() {
// 「前へ」「次へ」ボタンの表示・非表示を制御
prev.classList.remove('hidden');
next.classList.remove('hidden');
// いったん両方のボタンを表示状態に戻す
// これをしておくことで条件分岐がうまく働く
if (currentIndex === 0) {
prev.classList.add('hidden');
// 現在のスライドが最初(index 0)であれば、
// 「前へ」ボタンは不要なので、
// .hidden を追加して非表示にする
}
if (currentIndex === slides.length - 1) {
// 最後のスライドなら「次へ」ボタンを非表示にする
// slides.length - 1 = '最後のスライド'
next.classList.add('hidden');
}
}
function moveSlides() {
// スライドの位置を変更
const slideWidth = slides[0].getBoundingClientRect().width;
// 最初のスライド(li)の横幅を取得(レスポンシブ対応)
// → 画面サイズに応じて動的に計算される
// ※1枚目のスライドの「横幅」を調べて、
// → それを移動距離に使うため[0]としている
// getBoundingClientRect() = 要素の表示サイズや位置を取得
ul.style.transform = `translateX(${-1 * slideWidth * currentIndex}px)`;
// ulを左方向にスライド1枚分(幅)だけ移動させる
// -1倍してるのは「左に移動」だから(正だと右に動く)
}
function setupDots() {
// 丸ボタンの生成とクリックイベント登録
for(let i = 0; i < slides.length; i++) {
// 画像の数だけボタンを作るためループを回す
// (画像の数=slides.length)
const button = document.createElement('button');
// <button>要素をJavaScriptで生成(DOM生成)
button.addEventListener('click', () => {
// 丸ボタンをクリックした際の処理
currentIndex = i;
// i番目の画像を表示
updateDots();
// 関数の巻き上げ。丸ボタンの状態を更新
updateButtons();
// ボタンの状態(表示/非表示)を更新
moveSlides();
// スライドを移動させる
});
dots.push(button);
// 作成したボタンを配列dotsの末尾に追加
document.querySelector('nav').appendChild(button);
// <nav> 要素の末尾にこのボタンを追加してHTML上に表示
// appendChild = 指定の親要素の子要素として末尾に追加
dots[0].classList.add('current');
// 最初の丸ボタンにcurrentクラスを付けて現在のスライドを明示
}
}
function updateDots () {
// 丸ボタンの見た目を更新
dots.forEach(dot => {
dot.classList.remove('current');
});
// dotsの全ての要素に対してcurrentクラスを削除
dots[currentIndex].classList.add('current');
// 今クリックされたボタンにだけcurrentクラスを付ける
}
// 初期化処理:ページ読み込み時に一度だけ実行
updateButtons();
// ボタンの状態(表示/非表示)を更新
setupDots();
// ページが読み込んだ時に丸ボタンの動作を実行
next.addEventListener('click', () => {
// 「次へ」ボタンのクリックイベント
currentIndex++; // 現在のスライドを1つ進める
updateButtons(); // スライドを移動するたびにUIの状態を更新
updateDots(); // スライドを移動するたびに丸ボタンも更新
moveSlides(); // 実際にulのスライド位置を動かす
});
prev.addEventListener('click', () => {
// 「前へ」ボタンのクリックイベント
currentIndex--; // 現在のスライドを1つ戻す
updateButtons(); // スライドを移動するたびにUIの状態を更新
updateDots(); // スライドを移動するたびに丸ボタンも更新
moveSlides(); // // 実際にulのスライド位置を動かす
});
window.addEventListener('resize', () => {
// リサイズ時の調整(レスポンシブ対応)
moveSlides();
// 画面幅が変わったらリサイズする
});
}
ul.children
とは?
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
- 上のようなHTMLがあるとき、JSで以下のように書くと、
const ul = document.querySelector('ul');
const slides = ul.children;
HTMLCollection [
<li>a</li>,
<li>b</li>,
<li>c</li>
]
-
slides
には上記のような 「li要素たち」 が入っている -
children
は配列のように使えるが、forEach()
や.map()
は使えない
→forEach()
を使うには、.querySelectorAll('ul > li')
のように最初から NodeListとして取得した方が楽でモダン
項目 |
.children (HTMLCollection) |
.querySelectorAll() (NodeList) |
---|---|---|
.forEach() などの配列メソッド |
❌ 使えない(変換が必要) | ✅ そのまま使える |
length や[] インデックス指定 |
✅ 使える | ✅ 使える |
要素のフィルタやマッチング | ❌ 不便 | ✅ CSSセレクタで柔軟に指定できる |
DOMの構造依存 | 高い(親→子) | 低い(セレクタ指定だけでOK) |
モダンなJSに向いてる? | ❌ | ✅ |
↓ ポートフォリオの実装用に調べながら書き直したカルーセル
'use strict';
{
const slides = document.querySelectorAll('.cover__carousel > div');
// .cover__carousel 内のスライド(div要素)をすべて取得
// querySelectorAll → NodeList を返すため、forEach()が使える
//(子要素の.childrenと違って柔軟に扱える)
const dots = []; // ドットボタン(丸ボタン)を格納する配列
let currentIndex = 0; // 現在表示中のスライドのインデックス
let timer; // setIntervalのIDを保持する変数(のちにclearIntervalで停止するため)
// ドットボタンの生成&追加処理
function setupDots() {
const nav = document.createElement('nav');
// navタグを新たに作成 → ドットボタン群の意味的なまとまりを表す
// (スライドを切り替えるドットボタン群は
//「ナビゲーションの役割」を持つため、意味的に<nav>タグが適しており、
// アクセシビリティやSEOの面で望ましい選択である。)
nav.classList.add('dots'); // CSSで整えるためのクラス名
// slidesの数だけボタンを作る
slides.forEach((_, index) => {
// 第1引数 _ は「スライド要素そのもの」。
// ここでは使わないため「捨て変数」としてアンダーバーにしている
// 第2引数 index は0,1,2...と連番で渡ってくるインデックス番号
const button = document.createElement('button'); // ボタン生成
button.addEventListener('click', () => {
// クリックされたらその index を使ってスライドを切り替える
showSlide(index);
resetTimer(); // 自動再生のタイミングをリセット
});
dots.push(button); // 配列dotsに追加して管理
nav.appendChild(button); // navタグの末尾にボタンを追加
});
document.querySelector('.cover').appendChild(nav);
// .cover要素の末尾に<nav class="dots">を追加(HTML構造に反映)
dots[0].classList.add('current');
// 初期状態で最初のドットだけに .current をつける(強調表示)
}
// スライドとドットの表示を連動して更新
function showSlide(index) {
// index番目のスライドだけを表示、他を非表示(activeクラスで制御)
slides.forEach((slide, i) => {
slide.classList.toggle('active', i === index);
});
// ドットも同様に、index番目だけ current をつけて強調
dots.forEach((dot, i) => {
dot.classList.toggle('current', i === index);
});
currentIndex = index;
// 現在のインデックスを更新(次回以降の計算に必要)
}
// 自動でスライドを進めるタイマー処理
function startTimer() {
timer = setInterval(() => {
// 次に表示するスライド番号を計算:
// 例)今が2、スライドが3枚 → (2+1) % 3 = 0 → 最初に戻る
const nextIndex = (currentIndex + 1) % slides.length;
showSlide(nextIndex); // 次のスライドを表示
}, 5000); // 5000ミリ秒ごと(=5秒間隔)
}
// 手動操作のあとにタイマーをリセットする関数
function resetTimer() {
clearInterval(timer); // 前のタイマーを止めて
startTimer(); // 新しく開始し直す
}
// 初期化:ページ読み込み時に一度だけ実行
setupDots(); // ドットを作る
showSlide(0); // 最初のスライドを表示
startTimer(); // 自動再生開始
}
カウントダウンタイマー実装メモ
- 1.HTML側でタイマー表示とボタンを作る
<body>
<p id="timer">00:03</p>
<button id="btn">Start</button>
<script src="main.js"></script>
</body>
- 2.JSでまずは要素を取得+必要な変数を用意
- (複数の関数へ共通で使うので、関数の外に出しておく)
const timer = document.querySelector('#timer');
const btn = document.querySelector('#btn');
let endTime; // 終了時刻。値が変動するため定義はlet
let intervalId; // setInterval()で使う。値が変動するため定義はlet
- 3.イベントリスナーでスタートボタン押下時の挙動を書く
(「関数も変数も全部揃ったあとに実行処理を書く」流れなので、
コードの順番としては一番下の方の行になる) -
new Date()
:スタートボタンを押した時の日時を取得(※日時操作編の復習) -
getTime()
:基準日から経過ミリ秒を表す整数値に変換して計算 - +カウントダウンの時間分(任意) * 1000(ミリ秒に単位を揃える)=3秒(下記の場合)
-
setInterval(check, 100)
:「一定間隔で関数を呼び出す」ための関数
第1引数には繰り返し実行したい関数そのもの(check)、
第2引数には第1引数の関数をどれくらいの間隔で繰り返すかミリ秒で指定
(今回の場合は100ミリ秒ごとに呼び出す)
btn.addEventListener('click', ()=> {
endTime = new Date().getTime() + 3 * 1000;
btn.disabled = true;
// 一度ボタンをクリックしたら、
// タイマーが終了するまでボタンを押せないようにする
intervalId = setInterval(check, 100); });
}
- 4.残り時間を計算して表示する
check
関数を書く
(コードの順序としては1番最初) - 残り時間 = 終了時刻 - 現在時刻を計算する。
let countdown = endTime - new Date().getTime();
タイマー終了後に時間を戻す際に再代入するためletで宣言。
new Date()
:一定時間ごとに残り時間をチェックする時刻
※イベントリスナーのnew Date()
と用途が異なるため注意!こちらは終了時の現在時刻
そしてこちらもgetTime()
で単位をミリ秒に揃える -
if (countdown < 0)
でタイマー終了時の動作を書く clearInterval
:setInterval()
を停止するための関数。(intervalId)
を配置する- ここまでミリ秒表示だったので、分と秒表示に変換する
const totalSeconds = Math.floor(countdown / 1000);
小数点以下の値を切り捨てるためにMath.floor()
で囲み、
ミリ秒である変数countdown
を1000で割れば秒になる -
Math.floor()
:時間同士の差を求める関数(※日時操作編の復習)
分:const minutes = Math.floor(totalSeconds / 60);
秒:const seconds = totalSeconds % 60;
計算方法:
80秒 → 1分20秒
80 ÷ 60 = 1 余り 20
80 / 60 = 1.33333.... → 1(分)
80 % 60 = 20(秒) (復習:%で余りを出せる)
- 次に、分と秒を2桁表示にする=minutesとsecondsを整形し直した文字列を作る
(minutesFormatted
とかsecondsFormatted
とか、変数名は分かりやすく付ける)
分:const minutesFormatted = String(minutes).padStart(2, '0');
秒:const secondsFormatted = String(seconds).padStart(2, '0');
-
padStart()
:2桁で表示してその桁に満たなかったら、文字列の前を0で埋める
(※文字列操作編の復習!)
padStart()
は文字列にしか使えないので、String()
で変数を囲む -
textContent
でブラウザにカウントダウンを表示させる
function check () {
let countdown = endTime - new Date().getTime();
// タイマーが終了したら↓
if (countdown < 0) {
clearInterval(intervalId);
countdown = 3 * 1000;
// タイマー終了後に時間を戻す
btn.disabled = false;
// タイマーが終了後に再びボタンを押せるようにする
}
// 分と秒表示に変換
const totalSeconds = Math.floor(countdown / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
// 分と秒を2桁表示にする
const minutesFormatted = String(minutes).padStart(2, '0');
const secondsFormatted = String(seconds).padStart(2, '0');
// ブラウザにカウントダウンを表示させる
// ':'があるのでテンプレートリテラル使用
timer.textContent = `${minutesFormatted}:${secondsFormatted}`;
}
- 5.タイマーが始まって、ボタンが押せなくなった時の見た目をCSSで
.inactive
('無効'という意味)として定義してから、JSのcheck
関数:if (countdown < 0)
内(タイマー終了時)にbtn.classList.remove('inactive');
を、イベントリスナー(タイマー開始時)にbtn.classList.add('inactive');
を追加する
function check () {
if (countdown < 0) {
...
btn.disabled = false;
btn.classList.remove('inactive');
}
}
btn.addEventListener('click', ()=> {
...
btn.disabled = true;
btn.classList.add('inactive');
...
});
- JS完成形(フルコード)がこちら
'use strict';
{
function check () {
let countdown = endTime - new Date().getTime();
if (countdown < 0) {
clearInterval(intervalId);
countdown = 3 * 1000;
btn.disabled = false;
btn.classList.remove('inactive');
}
const totalSeconds = Math.floor(countdown / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const minutesFormatted = String(minutes).padStart(2, '0');
const secondsFormatted = String(seconds).padStart(2, '0');
timer.textContent = `${minutesFormatted}:${secondsFormatted}`;
}
const timer = document.querySelector('#timer');
const btn = document.querySelector('#btn');
let endTime;
let intervalId;
btn.addEventListener('click', ()=> {
endTime = new Date().getTime() + 3 * 1000;
btn.disabled = true;
btn.classList.add('inactive');
intervalId = setInterval(check, 100);
});
}
- 6.CSSでスタイルを整える。詳細は割愛するが、buttonのデフォルトスタイルの解除として
all: unset;
を使用しているため覚えておく
三択クイズ実装メモ
- クリックすると色が変わって、正解・不正解のテキストが追加されるアプリを作成
- 1.正解の選択肢には
correct
、不正解の選択肢にはwrong
というクラスをつける
<body>
<main>
<h1>三択クイズ</h1>
<section>
<h2>1の正解は?</h2>
<ul>
<li class="correct">選択肢 A</li>
<li class="wrong">選択肢 B</li>
<li class="wrong">選択肢 C</li>
</ul>
</section>
</main>
<script src="main.js"></script>
</body>
- 2.正解/不正解それぞれに文字色の変化や表示メッセージを追加する
.correct {
color: green;
}
.correct::after {
content: ' - 正解!';
}
.wrong {
color: red;
}
.wrong::after {
content: ' - 不正解...';
}
-
main.js
にて、配列を用意し、DOMを利用してブラウザに表示させる
- 3.問題文, 選択肢, 選択肢, 選択肢, 正解(0, 1, 2)を表す配列を作る
const quizzes = [ ['1の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 0], ['2の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 1], ['3の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 2], ];
- 4.
render
という関数を作り、quiz
を渡して、
関数内でHTMLのmain
要素を取得し、section
要素を新しくjS側で作り、
appendChild()
でsection
をmain
の最後の子要素として追加する
→HTMLの</main>
の直前に<section></section>
が生成される
(DOM編の復習!☞appendChild()
:親要素の 末尾(1番最後) に追加する)
- 5.
h2
要素もcreateElement()
で作り、そのtextContent
に、
quiz[0]
=問題文:'1の正解は?'を代入する
- 6.
section.appendChild(h2);
で、h2
をsection
の子要素として追加
→main.appendChild(section);
で生成した<section></section>
タグ内に<h2>1の正解は?</h2>
が生成される
- 7.
section.appendChild(ul);
で、ul
をsection
の子要素として追加
→main.appendChild(section);
で生成した<section></section>
タグ内、<h2>1の正解は?</h2>
の次に<ul></ul>
が生成される
※書く順番を間違えると、生成される要素の入れ子などがおかしくなるので注意!(現在は<section><h2>...</h2><ul></ul></section>
)。
- 8.
li
要素もli0
li1
li2
と番号を振って生成し、それぞれにquiz[1]
quiz[2]
quiz[3]
=選択肢A,B,Cを代入。ul.appendChild(li0)
...で、前の工程で生成した<ul></ul>
の中に<li>選択肢A</li>
...が生成される
- 9.選択肢をクリックした時の処理をイベントリスナーで書く
- 10.
render
関数の呼び出しをループ処理forEach
でまとめる
'use strict';
{
function render(quiz) {
const main = document.querySelector('main');
const section = document.createElement('section');
const h2 = document.createElement('h2');
h2.textContent = quiz[0];
const ul = document.createElement('ul');
const li0 = document.createElement('li');
li0.textContent = quiz[1]; // 選択肢Aを代入
li0.addEventListener('click', () => {
if (quiz[4] === 0) { // 回答が0なら
li0.classList.add('correct'); // 正解
} else { // それ以外は
li0.classList.add('wrong'); // 不正解
}
});
const li1 = document.createElement('li');
li1.textContent = quiz[2]; // 選択肢Bを代入
li1.addEventListener('click', () => {
if (quiz[4] === 1) { // 回答が1なら
li1.classList.add('correct'); // 正解
} else { // それ以外は
li1.classList.add('wrong'); // 不正解
}
});
const li2 = document.createElement('li');
li2.textContent = quiz[3]; // 選択肢Cを代入
li2.addEventListener('click', () => {
if (quiz[4] === 2) { // 回答が2なら
li2.classList.add('correct'); // 正解
} else { // それ以外は
li2.classList.add('wrong'); // 不正解
}
});
ul.appendChild(li0);
ul.appendChild(li1);
ul.appendChild(li2);
section.appendChild(h2);
section.appendChild(ul);
main.appendChild(section);
}
// 問題文, 選択肢, 選択肢, 選択肢, 正解(0, 1, 2)
const quizzes = [
['1の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 0],
['2の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 1],
['3の正解は?', '選択肢 A', '選択肢 B', '選択肢 C', 2],
];
quizzes.forEach((quiz) => {
render(quiz);
});
}
- ひと通り要素を生成できたため、HTMLから
<section>
ごと消しても問題なく動くことを確認する
<body>
<main>
<h1>三択クイズ</h1>
</main>
<script src="main.js"></script>
</body>
メモ帳 実装メモ
- 保存ボタンを押すことで、フォームのデータをリロードしても消えないシステムを作る
- 1.HTMLのテキストエリア部分にid
text
、「保存しました」のメッセージにidmessage
、削除ボタンにidclear
、保存ボタンにidsave
を付ける
<body>
<main>
<h1>メモ帳</h1>
<textarea id="text"></textarea>
<div class="container">
<span id="message">保存しました</span>
<button id="clear">削除</button>
<button id="save">保存</button>
</div>
</main>
<script src="main.js"></script>
</body>
- 2.JSにて
#text
#save
#message
を取得し、保存ボタンに対してクリックイベントを設定する
- 3.localStorage(キーと値をペアで保存する仕組み) でデータを保存する
localStorage.setItem('memo', text.value);
:localStorage.setItem
メソッドを呼び出し、第一引数にはキーの名前:'memo'
、第二引数には値:text.value
(テキストボックスに入力した内容)を入れる- 入力された内容が保存されたか確かめるには、開発者ツール>アプリケーション>ストレージ>ローカルストレージ内のファイルをクリック。キーと値が表示される (※リロードしてもキーと値が消えないか確認!)
- 4.「保存しました」メッセージの挙動としてCSSに
.appear
クラスを作り、opacity: 1;
、+.span
クラスにtransition: opacity 300ms;
を設定する
- 5.JSにて、保存ボタンが押されたら
appear
をclassList.add
でクラス追加される=「保存しました」メッセージが表示されるようにし、1秒後(1000ミリ秒後)にappear
クラスを外すことで「保存しました」メッセージを消す
(DOM編の復習!☞要素のスタイルを変更するには、CSSでクラスを定義してから、classList
を使う。クラス追加:classList.add
、クラス削除:classList.remove
)
- 6.テキストボックスに入力した内容がリロードしても消えないように、
localStorage.getItem()
で localStorage からデータを取得。引数には取得したいキーmemo
を指定し、text.value
に代入する-
if
文で、初期読み込み時、テキストボックスが空の時の処理をnull
として指定する →これで、localStorageとテキストボックスの挙動が揃う
-
- 7.削除ボタンの処理を
localStorage.removeItem('memo')
で設定、if
文でconfirm('本当に削除しますか?')
の確認ダイアログ表示も追加する
'use strict';
{
const text = document.querySelector('#text');
const save = document.querySelector('#save');
const message = document.querySelector('#message');
if (localStorage.getItem('memo') === null) {
text.value = '';
} else {
text.value = localStorage.getItem('memo');
}
save.addEventListener('click', () => {
message.classList.add('appear');
setTimeout(() => {
message.classList.remove('appear');
}, 1000);
localStorage.setItem('memo', text.value);
});
clear.addEventListener('click', () => {
if (confirm('本当に削除しますか?') === true) {
text.value = '';
localStorage.removeItem('memo');
}
});
}
ストップウォッチ実装メモ
- 1.タイマー表示と各ボタンに
id
を割り当てる
<body>
<div class="container">
<div id="timer">00:00.000</div>
<div class="controls">
<div class="btn" id="start">Start</div>
<div class="btn" id="stop">Stop</div>
<div class="btn" id="reset">Reset</div>
</div>
</div>
<script src="main.js"></script>
</body>
- 2.JSで各
id
を取得する - 3.
let
で変数startTime
を宣言、スタートボタンにイベントリスナーでクリックイベントを設定し、startTime = Date.now();
で基準となる日時からの経過ミリ秒を計算する (※getTime
と混同注意!)。また、カウントアップ用のcountUp
関数を定義し、分,秒,ミリ秒の表示をそれぞれ作る - (復習!日時操作編☞ 現在の日時を取得:
new Date()
)
- 重要:
Date.now()
とgetTime()
の違い
どちらも「今の時間をミリ秒(1/1000秒)で数字にしてくれる」関数
使い方 | 説明 |
---|---|
Date.now() |
今この瞬間のミリ秒を一発でゲットできる(シンプル) |
new Date().getTime() |
一度 Date の形にしてから、そこからミリ秒を取り出す方法(少しまわりくどい) |
☞今回は「ストップウォッチのスタート時刻をミリ秒で覚えておく」だけなので、Date.now()
が一番カンタンで早い。+カウントダウンタイマーで使用したnew Date().getTime()
をDate.now()
に書き換えても同じ結果になるので、今後はDate.now()
で書いてもOKだがよく復習して書くこと!
- 4.ストップボタンもイベントリスナーで作る。
countUp
関数で定義したsetTimeout
の逆、clearTimeout
で止める(※setInterval
clearInterval
と混同注意!)。setTimeout
用のIDもlet timeoutId;
で定義し、timeoutId
はclearTimeout()
に渡す
- 重要:
setTimeout
とsetInterval
の違い
関数 | なにするの? |
---|---|
setTimeout(fn, 時間) |
「◯ミリ秒後に1回だけ」関数を動かす |
setInterval(fn, 時間) |
「◯ミリ秒ごとにずっと」関数を動かす |
☞「毎回決まった時間(例えば10ミリ秒)で動かすけど、間に合わなくてもどんどん動かしてしまうので、だんだんズレてしまうことがある」setInterval
とは違い、今回は「1回終わったら、次は10ミリ秒待ってからまた動かす」setTimeout
で、ズレが出にくくて、より正確なタイマーにしている
-
setInterval
(カウントダウンなど、簡単に書ける):
setInterval(fn, 100);
-
setTimeout
(ストップウォッチなど、ズレ補正しやすい):
function loop() { fn(); setTimeout(loop, 10); } loop();
☞出力結果(画面上の表示)はほぼ同じだが、setInterval
は時間のズレが蓄積される可能性があるため、正確な時間管理が必要な場面ではsetTimeout
を再帰的に使うほうが適している
- 5.リセットボタンのイベントも用意し、
textContent
で元の表記に戻す
- ☆タイマーを止めた時刻から再開できるようにする
- 6.変数
elapsedTime
(経過した時間)を定義し初期値を0
にする - 7.ストップした時の挙動として
Date.now() - startTime
:今回スタートしてから今までの時間をelapsedTime
に加算し、合計時間として保存する - 8.再開した時の挙動として
countUp()
内、const d =
のnew Date()
内に、Date.now() - startTime
=再開してからの時間へ+elapsedTime
のそれまでに記録してた時間を足す - 9.リセットした時の挙動として、
elapsedTime
の初期値を0
にする
- ☆それぞれのボタンを押した時に、押すべきではないボタンを無効化する
状態 | Startボタン | Stopボタン | Resetボタン |
---|---|---|---|
初期状態 | 有効 | 無効 | 無効 |
稼働状態 | 無効 | 有効 | 無効 |
停止状態 | 有効 | 無効 | 有効 |
- 10.
setButtonStateInitial
(初期状態)、setButtonStateRunning
(稼働状態)、setButtonStateStopped
(停止状態)、3つの関数を作り、classList.add/remove('inactive');
でそれぞれstart
stop
reset
時の状態を指定。(※div
要素を使う場合にはdisabled
プロパティを使えない)
- 11.いま作った3つの関数を呼び出す
setButtonStateInitial
(初期状態)はページを読み込んですぐに必要なので、スタートボタン クリックイベントの前、+リセットボタン クリックイベントの中、setButtonStateRunning
(稼働状態)はスタートボタン クリックイベントの中、setButtonStateStopped
(停止状態)はストップボタン クリックイベントの中で呼び出す
- 12.二重に各関数を走らせない対策として、各クリックイベント内に「そのボタンが“無効状態(=グレー表示)だったら、何もせずに終了」する処理を追加する。
if (start/stop/reset.classList.contains('inactive') === true) { return; }
:もしそのボタンがinactive
というクラスを含んでいたら(=無効化されていたら)、それ以上の処理をせずにreturn
で抜ける
'use strict';
{
const timer = document.querySelector('#timer');
const start = document.querySelector('#start');
const stop = document.querySelector('#stop');
const reset = document.querySelector('#reset');
let startTime;
let timeoutId;
let elapsedTime = 0;
function countUp() {
const d = new Date(Date.now() - startTime + elapsedTime);
// dateのd。再開してからの時間+それまでに記録してた時間=通算の経過時間
const m = String(d.getMinutes()).padStart(2, '0');
// minuteのm。padStartで2桁表示に。文字列用の関数なのでStringで囲んでおく
const s = String(d.getSeconds()).padStart(2, '0');
// secondのs。padStartで2桁表示に。文字列用の関数なのでStringで囲んでおく
const ms = String(d.getMilliseconds()).padStart(3, '0');
// ミリ秒のms。padStartで3桁表示に。文字列用の関数なのでStringで囲んでおく
timer.textContent = `${m}:${s}.${ms}`;
// テンプレートリテラルで00:00:000表示にする
timeoutId = setTimeout(() => {
countUp();
}, 10); // 10ミリ秒ごとに呼び出す
}
function setButtonStateInitial() { // 初期状態
start.classList.remove('inactive');
stop.classList.add('inactive');
reset.classList.add('inactive');
}
function setButtonStateRunning() { // 稼働状態
start.disabled = true;
stop.disabled = false;
reset.disabled = true;
}
function setButtonStateStopped() { // 停止状態
start.disabled = false;
stop.disabled = true;
reset.disabled = false;
}
setButtonStateInitial();
// ページを読み込んですぐにボタンを初期状態にする
start.addEventListener('click', () => {
if (start.classList.contains('inactive') === true) {
return;
}
setButtonStateRunning();
// スタートボタン押下時にボタンを稼働状態にする
startTime = Date.now();
countUp();
});
stop.addEventListener('click', () => {
if (stop.classList.contains('inactive') === true) {
return;
}
setButtonStateStopped();
// ストップボタン押下時にボタンを停止状態にする
clearTimeout(timeoutId);
elapsedTime += Date.now() - startTime;
// スタートしてから今までの時間+`elapsedTime`=合計時間
});
reset.addEventListener('click', () => {
if (reset.classList.contains('inactive') === true) {
return;
}
setButtonStateInitial();
// リセットボタン押下時にボタンを初期状態にする
timer.textContent = '00:00.000';
elapsedTime = 0;
// リセットボタン押下時に経過時間も0にする
});
}
- 13.CSSでスタイルを整える。詳細は割愛するが、ボタンを連打してもテキストが選択されないようにするプロパティとして
user-select: none;
を使用しているため覚えておく
Todo管理アプリ実装メモ
- 項目を入力し、Addボタンを押すとリストに追加されていき、チェックボックスでTodoリストの管理ができるアプリを作る
- ×ボタンで個別に削除や、まとめて削除できる機能も実装する
- 1.HTMLにて各要素をマークアップする
<body>
<div class="container">
<h1>
Todos
<button id="purge">Purge</button> <!-- IT用語で「削除」-->
</h1>
<ul id="todos">
<!-- <li>
<label>
<input type="checkbox">
<span>
aaa
</span>
</label>
<button>x</button>
</li>
<li>
<label>
<input type="checkbox">
<span>
bbb
</span>
</label>
<button>x</button>
</li>
<li>
<label>
<input type="checkbox">
<span>
ccc
</span>
</label>
<button>x</button>
</li> -->
<!-- li要素内→JSに移植 -->
</ul>
<form id="add-form">
<input type="text"> <!-- テキストボックス -->
<button>Add</button> <!-- 項目追加ボタン -->
</form>
</div>
</body>
- 2.CSSでスタイルを整える。詳細は割愛するが、チェックボックスがチェックされた時、リストを打ち消し線で消す時のスタイリングとして、HTMLにて
input
要素内の項目を<span>
タグで囲ってから、li input:checked + span { color: #aaa; text-decoration: line-through; }
を書く
- 3.JSにて、タスクの名前と「チェックが終わったかどうか」=
isCompleted
を、配列todos
にまとめて管理しておく。それを元に、画面にタスクを表示する処理をまとめた関数renderTodos
を作り、最後に実行する
const todos = [
{title: 'aaa', isCompleted: false}, // ← この1つが"todo"
{title: 'bbb', isCompleted: false},
{title: 'ccc', isCompleted: false},
];
...
renderTodos();
// ページを読み込んだ時点で、
// 初期のTodo3件(aaa, bbb, ccc)を表示させる
- 4.
renderTodos
の中で、タスクを1つずつ取り出すためにforEach
を使い、その中で、1つのタスクtodo
をもとに表示を作る関数renderTodo
を使う
const renderTodos = () => {
todos.forEach((todo) => {
// ↑ todo = {title: 'aaa', isCompleted: false}
renderTodo(todo);
});
};
// todos配列(初期状態で3件)を1件ずつ取り出して、
// renderTodo()関数を呼び出して表示する
- 5.
renderTodo
関数の中では、label
・input
(チェックボックス)・span
(タスク名の文字)・button
(削除ボタン)・li
をcreateElement
で作り、appendChild
を使って順番につなげ、li
タグの中にまとめる
const renderTodo = (todo) => {
// todoオブジェクトを引数にとる関数
// HTML内に「1つ分のTodo項目」を追加する
/*
- li
- label
- input
- span
- button
*/
const input = document.createElement('input');
// <input>要素(チェックボックス)を作成
input.type = 'checkbox';
// <input>のタイプをチェックボックスに指定
input.checked = todo.isCompleted;
// Todoの完了状態(true/false)に応じてチェック状態にする
const span = document.createElement('span');
// タスクタイトル(例:"aaa")を表示するためのテキスト用の<span>を作成
span.textContent = todo.title;
// todo.title(例:"aaa")をテキストとして表示
const label = document.createElement('label');
// <label>要素(inputとspanをまとめるための親要素)を作成
label.appendChild(input);
label.appendChild(span);
// チェックボックス(input)とタイトル(span)をラベルの中に追加
const button = document.createElement('button');
// 削除用の<button>要素を作成
button.textContent = 'x';
// ボタンの中に「x」という文字を表示
...
const li = document.createElement('li');
// <label>と<button>をまとめる<li>を作成
li.appendChild(label);
li.appendChild(button);
// <li>の中にチェック付きタイトル(label)と削除ボタン(button)を追加
document.querySelector('#todos').appendChild(li);
// HTMLの<ul id="todos">内に、<li>要素を追加する。
// = 画面上にTodo1件分が表示される
};
-
※
todos
はリスト・配列のまとまり、todo
は'今扱ってる1個'(forEachで取り出した1個のオブジェクト)で使い分ける
- 6.
form
でタスクを追加するために、HTML側のform
タグに追加したid="add-form"
を使い、document.querySelector('#add-form')
でそのフォーム要素を取得する - 7.そのフォームに対して、
.addEventListener('submit', (e) => { ... })
でsubmit
イベントの処理を追加する。(これは、ユーザーが「Add」ボタンを押したときに発火する)
<form id="add-form">
<input type="text"> <!-- テキストボックス -->
<button>Add</button> <!-- 項目追加ボタン -->
</form>
document.querySelector('#add-form').addEventListener('submit', (e) => {
e.preventDefault(); // ページ遷移を防ぐ
...
});
- ※
submit
イベントが起きると、本来ブラウザはページを再読み込みしてしまうが、それを防ぐためにe.preventDefault();
を記述して、標準の送信処理をキャンセルする - ※DOM編の復習☞:
submit
イベント:formタグ内で送信ボタンが押されたときに発生するイベント。入力された内容を処理したり、別の動作(画面表示など)に繋げたい時に使う -
e.preventDefault();
:ブラウザの“標準の動作”(再読み込み)をキャンセルする。submit
イベントと必ずセットで使う
- 8.
submit
イベントリスナー内、todo
オブジェクトのtitle
部分にて、document.querySelector('#add-form input')
で、フォーム内のinput
要素を取得し、その.value
(入力されたテキスト)を使って、新しいタスクオブジェクトを作成する
また、isCompleted
部分は後からチェックボックスをオンにした時true
に変わるように、最初はfalse
に設定する
document.querySelector('#add-form').addEventListener('submit', (e) => {
e.preventDefault(); // ページ遷移を防ぐ
...
const todo = {
title: input.value, // 入力されたテキスト
isCompleted: false, // まだ完了していないタスク
};
renderTodo(todo); // タスクを画面に追加
...
});
- 9.そして、
title
とisCompleted
の配列にしたtodo
オブジェクトを、あらかじめ定義しておいたrenderTodo()
関数に引数として渡すことで、画面上にそのタスクが表示される
- 10.また、
document.querySelector('#add-form input')
はinput
という定数に当てはめ、コードを簡略化する(※ここまでで使用していたdocument.querySelector('#add-form input')
部分はinput
に書き換える)。そして、input.value = '';
の空文字で入力された値を消し、input.focus();
フォームをフォーカスする処理を書く
document.querySelector('#add-form').addEventListener('submit', (e) => {
e.preventDefault(); // ページ遷移を防ぐ
const input = document.querySelector('#add-form input');
const todo = {
title: input.value, // 入力されたテキスト
isCompleted: false, // まだ完了していないタスク
};
renderTodo(todo); // タスクを画面に追加
input.value = ''; // 入力された値を消す
input.focus(); // フォームをフォーカスする
});
- 11.また、
renderTodo
関数内のbutton.textContent = 'x';
の下に削除ボタンのイベントli.remove();
を追加する。本当に消すか?の確認もif
文で書く
const renderTodo = (todo) => {
...
const button = document.createElement('button');
// 削除用の<button>要素を作成
button.textContent = 'x';
// ボタンの中に「x」という文字を表示
button.addEventListener('click', () => {
// 削除ボタンの処理
// if (confirm('Sure?') === false) {
if (!confirm('Sure?')) { // 消す前に確認
return;
}
li.remove();
});
...
};
- ☆タスクを保存・復元できるように、
localStorage
とJSON
を活用する - ToDoアプリをリロードしても消えないようにするため
localStorage
を使う -
前提知識
localStorage.setItem()
で保存できるのは文字列だけ- 配列などのオブジェクトを保存したいときは、
JSON.stringify()
で文字列に変換し、保存したデータを取り出す時はJSON.parse()
で元の形式(配列など)に戻す
// 参考
localStorage.setItem('todos', JSON.stringify(todos));
console.log(JSON.parse(localStorage.getItem('todos')));
- ※メモ帳 実装編の復習☞
- localStorage(キーと値をペアで保存する仕組み) でデータを保存する
- 第一引数にはキーの名前、第二引数には値を入れる
- 入力された内容が保存されたか確かめるには、開発者ツール>アプリケーション>ストレージ>ローカルストレージ内のファイルをクリック。キーと値が表示される (※リロードしてもキーと値が消えないか確認!)
- キーは右クリックで削除できる
- コンソール画面へ値を呼び出すには
getItem()
を使う
- 12.JSコード冒頭の配列部分を書き替える
(紛らわしいが、次に実装する時はここから始めること!) - タスクをブラウザに保存できるようにするため、最初に
localStorage
をチェックして、保存されていればそれを読み込む、なければ空の配列[]
を作る、という処理を実装する
let todos;
// todosという変数だけを先に用意
// あとで保存済みのデータを代入するためlet
if (localStorage.getItem('todos') === null) {
todos = [];
// localStorageにtodosという保存データが
// まだ存在していなければ、空の配列を代入
} else {
todos = JSON.parse(localStorage.getItem('todos'));
// localStorageに保存されていたデータがあれば、
// それを元の配列に戻して代入する
}
- 13.加えて、
submit
イベントリスナー内、呼び出し部分renderTodo(todo);
の下あたりに追記で、ユーザーがフォームに入力したタスクtodo
を、現在のタスクリスト(todos
配列)に追加する処理を追記する -
(※
push()
は配列の末尾に要素を追加するメソッド+todo
はこのオブジェクト☞{ title: "入力された文字列", isCompleted: false }
)
- 14.
todos
配列を JSON文字列に変換してブラウザの中localStorage
に保存する。localStorage.setItem('todos', JSON.stringify(todos));
document.querySelector('#add-form').addEventListener('submit', (e) => {
...
const todo = {
title: input.value, // 入力されたテキスト
isCompleted: false, // まだ完了していないタスク
};
renderTodo(todo); // タスクを画面に追加
todos.push(todo);
// ユーザーがフォームに入力したタスク`todo`を、
// 現在のタスクリスト(`todos`配列)の末尾に追加
localStorage.setItem('todos', JSON.stringify(todos));
// `todos`配列をJSON文字列に変換して
// ブラウザの中(localStorage)に保存
...
});
- ☞ここまで書くと、
localStorage
が空の状態から、テキストフォームにタスクを入力し、Add
ボタンでタスクを追加する動作が出来るようになる。(リロードしてもタスクは消えずに残っている)
- 15.次に、
todo
を削除した時にもlocalStorage
のデータが更新されるように実装する。submit
イベントリスナー内、todo
のプロパティにid: Date.now();
でUNIXタイムスタンプを追加し、console.table(todos);
=表形式でtodos
のコンソールを出す
document.querySelector('#add-form').addEventListener('submit', (e) => {
...
const todo = {
id: Date.now(), // UNIXタイムスタンプを追加
title: input.value, // 入力されたテキスト
isCompleted: false, // まだ完了していないタスク
};
renderTodo(todo); // タスクを画面に追加
todos.push(todo);
// ユーザーがフォームに入力したタスク`todo`を、
// 現在のタスクリスト(`todos`配列)の末尾に追加
console.table(todos); // 表形式でコンソールを出す
localStorage.setItem('todos', JSON.stringify(todos));
// `todos`配列をJSON文字列に変換して
// ブラウザの中(localStorage)に保存(新しいタスクを追加した時用)
});
- 16.
renderTodo
関数の削除ボタン実装のあたりに追記で、todos.filter()
として、todos
配列から「クリックされたタスク以外」を抽出して再構築し、 さらに再構築されたtodos
配列をlocalStorage
に上書き保存する filter()
:配列から特定の条件で要素を抽出して、新しい配列を作る
const renderTodo = (todo) => {
...
const button = document.createElement('button');
// 削除用の<button>要素を作成
button.textContent = 'x';
// ボタンの中に「x」という文字を表示
button.addEventListener('click', () => {
// 削除ボタンの処理
// if (confirm('Sure?') === false) {
if (!confirm('Sure?')) { // 消す前に確認
return;
}
li.remove();
todos = todos.filter((item) => {
return item.id !== todo.id;
// todos配列から「クリックされたタスク以外」を抽出して再構築
// item.id(既存データ)とtodo.id(削除対象)を比較
});
localStorage.setItem('todos', JSON.stringify(todos));
// 再構築されたtodos配列をlocalStorageに上書き保存
// 削除後の最新状態のtodosをlocalStorageに保存しなおすため
});
...
};
- ☞ここまで書くと、リスト削除後にリロードしても削除が保持されている
- ☆リロードするとチェックが外れるバグがあるため直す
- 17.
renderTodo
関数の<input>
要素(チェックボックス)実装のあたりに、チェック状態の保存:isCompleted
をlocalStorage
に反映させる処理を追記
const renderTodo = (todo) => {
const input = document.createElement('input');
// <input>要素(チェックボックス)を作成
input.type = 'checkbox';
// <input>のタイプをチェックボックスに指定
input.checked = todo.isCompleted;
// Todoの完了状態(true/false)に応じてチェック状態にする
input.addEventListener('change', () => { // ←ここから追記
// チェックボックスの状態(ON/OFF)が変わったときに実行
todos.forEach((item) => {
// todos配列の中の各タスク(item)を1つずつ取り出す
if (item.id === todo.id) {
// 表示中のtodo(引数)と一致するidを持つitemを探す
item.isCompleted = !item.isCompleted;
// isCompletedの状態を反転(true⇔false)させる
// チェックを入れる or 外すの切り替えを反映
}
});
localStorage.setItem('todos', JSON.stringify(todos));
// todos配列全体をlocalStorageに保存しなおす(変更を永続化)
});
...
};
- 18.繰り返し使用している
localStorage.setItem('todos', JSON.stringify(todos));
(todos
配列をlocalStorage
に上書き保存する処理)をsaveTodos
という変数にまとめ、使用箇所を全てsaveTodos();
に書き換える
const saveTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos));
};
...
saveTodos();
- ☆まとめてリスト削除する
Purge
ボタンの実装 - 19.コード下部に完全に新たなイベントリスナーとして
#purge
のクリックイベントを追加 - 「完了チェックを入れた
todo
だけ」を一括削除=todos
配列の中から完了していない(isCompleted === false
)ものだけを抽出し、isCompleted === false
→ チェックされていないtodo
だけを残す
document.querySelector('#purge').addEventListener('click', () => {
todos = todos.filter((todo) => {
// todos配列の中から「完了していない
// (isCompleted === false)」ものだけを抽出して上書きする
return todo.isCompleted === false;
// チェックがついていないものだけを残す(trueは削除される)
});
saveTodos();
// localStorage.setItem('todos', JSON.stringify(todos));
// 残ったtodosをlocalStorageに保存(削除を反映させる)
});
- 20.引き続き
#purge
のクリックイベントに追記 - 削除ボタンの処理でも使用した、
if
文での「本当に消してよいか?」確認 - 削除結果を正しく反映させるため、いったん画面上のリストを全削除し、未完了のタスクだけを再表示する処理を追加
document.querySelector('#purge').addEventListener('click', () => {
if (!confirm('Sure?')) { // 消す前に確認
return;
}
...
document.querySelectorAll('#todos li').forEach((li) => {
li.remove();
// 現在画面に表示されているすべての<li>(タスク表示)を一旦全削除
});
renderTodos();
// localStorageから再取得 → 未完了タスクだけを画面に再描画する
});
- ↓ JSの完成コードがこちら
'use strict';
{
// const todos = [
// {title: 'aaa', isCompleted: false}, ←この1つが "todo"
// {title: 'bbb', isCompleted: true},
// {title: 'ccc', isCompleted: false},
// {title: 'ddd', isCompleted: false}
// ]; ↓配列データをlocalStorage仕様に書き替える↓
let todos;
// todosという変数だけを先に用意
// あとで保存済みのデータを代入するためlet
if (localStorage.getItem('todos') === null) {
todos = [];
// localStorageにtodosという保存データが
// まだ存在していなければ、空の配列を代入
} else {
todos = JSON.parse(localStorage.getItem('todos'));
// localStorageに保存されていたデータがあれば、
// それを元の配列に戻して代入する
}
const saveTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos));
};
const renderTodo = (todo) => {
// todoオブジェクトを引数にとる関数
// HTML内に「1つ分のTodo項目」を追加する
/*
- li
- label
- input
- span
- button
*/
const input = document.createElement('input');
// <input>要素(チェックボックス)を作成
input.type = 'checkbox';
// <input>のタイプをチェックボックスに指定
input.checked = todo.isCompleted;
// Todoの完了状態(true/false)に応じてチェック状態にする
input.addEventListener('change', () => {
// チェックボックスの状態(ON/OFF)が変わったときに実行
todos.forEach((item) => {
// todos配列の中の各タスク(item)を1つずつ取り出す
if (item.id === todo.id) {
// 表示中のtodo(引数)と一致するidを持つitemを探す
item.isCompleted = !item.isCompleted;
// isCompletedの状態を反転(true⇔false)させる
// チェックを入れる or 外すの切り替えを反映
}
});
saveTodos();
// localStorage.setItem('todos', JSON.stringify(todos));
// todos配列全体をlocalStorageに保存しなおす(変更を永続化)
});
const span = document.createElement('span');
// タスクタイトル(例:"aaa")を表示するためのテキスト用の<span>を作成
span.textContent = todo.title;
// todo.title(例:"aaa")をテキストとして表示
const label = document.createElement('label');
// <label>要素(inputとspanをまとめるための親要素)を作成
label.appendChild(input);
label.appendChild(span);
// チェックボックス(input)とタイトル(span)をラベルの中に追加
const button = document.createElement('button');
// 削除用の<button>要素を作成
button.textContent = 'x';
// ボタンの中に「x」という文字を表示
button.addEventListener('click', () => {
// 削除ボタンの処理
// if (confirm('Sure?') === false) {
if (!confirm('Sure?')) { // 消す前に確認
return;
}
li.remove();
todos = todos.filter((item) => {
return item.id !== todo.id;
// todos配列から「クリックされたタスク以外」を抽出して再構築
// item.id(既存データ)とtodo.id(削除対象)を比較
});
saveTodos();
// localStorage.setItem('todos', JSON.stringify(todos));
// 再構築されたtodos配列をlocalStorageに上書き保存
// 削除後の最新状態のtodosをlocalStorageに保存しなおすため
});
const li = document.createElement('li');
// labelとbuttonをまとめる<li>を作成
li.appendChild(label);
li.appendChild(button);
// <li>の中にチェック付きタイトル(label)と削除ボタン(button)を追加
document.querySelector('#todos').appendChild(li);
// HTMLの<ul id="todos">内に、<li>要素を追加する。
// = 画面上にTodo1件分が表示される
};
const renderTodos = () => {
todos.forEach((todo) => {
// todo = {title: 'aaa', isCompleted: false}
renderTodo(todo);
});
};
// todos配列(初期状態で3件)を1件ずつ取り出して、
// renderTodo()関数を呼び出して表示する
document.querySelector('#add-form').addEventListener('submit', (e) => {
e.preventDefault(); // ページ遷移を防ぐ
const input = document.querySelector('#add-form input');
const todo = {
id: Date.now(), // UNIXタイムスタンプを追加
title: input.value, // 入力されたテキスト
isCompleted: false, // まだ完了していないタスク
};
renderTodo(todo); // タスクを画面に追加
todos.push(todo);
// ユーザーがフォームに入力したタスク`todo`を、
// 現在のタスクリスト(`todos`配列)の末尾に追加
console.table(todos); // 表形式でコンソールを出す
saveTodos();
// localStorage.setItem('todos', JSON.stringify(todos));
// `todos`配列をJSON文字列に変換して
// ブラウザの中(localStorage)に保存(新しいタスクを追加した時用)
input.value = ''; // 入力された値を消す
input.focus(); // フォームをフォーカスする
});
document.querySelector('#purge').addEventListener('click', () => {
if (!confirm('Sure?')) { // 消す前に確認
return;
}
todos = todos.filter((todo) => {
// todos配列の中から「完了していない
// (isCompleted === false)」ものだけを抽出して上書きする
return todo.isCompleted === false;
// チェックがついていないものだけを残す(trueは削除される)
});
saveTodos();
// localStorage.setItem('todos', JSON.stringify(todos));
// 残ったtodosをlocalStorageに保存(削除を反映させる)
document.querySelectorAll('#todos li').forEach((li) => {
li.remove();
// 現在画面に表示されているすべての<li>(タスク表示)を一旦全削除
});
renderTodos();
// localStorageから再取得 → 未完了タスクだけを画面に再表示する
});
renderTodos();
// ページを読み込んだ時点で、
// localStorage に保存されている Todo をすべて画面に表示する
}