Web Developer基礎固めの部屋
改めてWEB Developerとしての基礎を固める
・JavaScriptを自分でコントロールしている感を得るために基礎を固める
・そもそものWEBの基礎から体系的に学んで知識を定着する
・エンジニアとしてのマインドセット、考え方、解決方法を学ぶ
Udemy講座【世界で70万人が受講】Web Developer Bootcamp 2023(日本語版)を受講
・演習問題は全て解く
・真似するだけで終わらない、理解してから進む
・時間をかけてOK、理解することの方が大事。
・理解するために些細なことでもZennのスクラップに投稿していく
day1 10/21
インターネットとは
様々な情報を運ぶためのインフラ
メール、WEB、ファイル、ゲームなど...etc
データ転送をするのはルーティングを行なっている。
様々なデバイスが繋がっている。
WEBとは
ワールド・ワイド・ウェブ
インターネットを介して、文書などのリソースを提供する情報システム
→これはURLが割り当てられる。ページもリソースと呼べる。
やり取りはHTTP(HTTPS)でやり取りされる。これが基盤になる。
→プロトコル
サーバー(WEBサーバー)とは
リクエストを受け取ってレスポンスを返す。WEBでリクエストを受け付けることのできる機器
これらがHTTPによって成り立っている。
クライアントとは
リクエストする側はクライアント、自分自身、自分のパソコン
クライアントサイド、サーバーサイドで分かれている。
クライアントサイドからリクエストを送ると、サーバーは部品と説明書を返す。
それをブラウザで組み立てる。
例)ikeaで部品と説明書を買って、家で組み立てるようなもの!完成した画面を返すわけじゃない!
HTML,CSS,JavaScriptをブラウザに返す。それをブラウザが解釈して見やすいように画面を組み立てている。
フロントエンドとバックエンドの違い
フロントエンド
ブラウザからリクエストを飛ばすのはフロントエンド
レストランで言うとウェイター
言語:HTML,CSS,JavaScript
フロントの表示に集中する
バックエンド
リクエストを受け取ったサーバーが調理してフロントに送り返す
レストランで言うとキッチン
言語:Java,Python,C++,etc...
処理に集中する
HTML,CSS,JavaScript
HTML 名詞 骨組み・物 例)恐竜
CSS 形容詞 見た目・装飾 例)紫色の
JavaScript 動詞 動き・機能 例)踊っている
day2 10/22
Formについて
actionを指定したところに対して、httpリクエストを送ることができる。
例)search,
<form action=""></form>
action属性:【どこに】フォームの情報を送信するかを指定する。
method属性:フォームを送信する際に、ブラウザーが使用するHTTPメソッドを指定する
httpでフォームactionで送信するとURLにパスワードとかもURLに反映してしまう。これは非常によろしくない。そんな時はpostを使用する。
【応用と遊び】自分のサイトからYoutube検索をしてみる
改めてform,actionの仕組みについて理解できた!
今までなんとなくだったから裏側の仕組みを知って応用して動くと楽しい。
<!-- どこで検索するか -->
<form action="https://www.youtube.com/results">
<!-- https://www.youtube.com/results?search_query=検索結果 -->
<input type="text" name="search_query" placeholder="検索名" />
<button type="submit">Youtubeで検索</button>
</form>
Youtubeはsearch_query=で検索されているので、name属性を合わせて、formの送信の向き先をYoutubeの検索結果URLから拝借して合わせれば、自作サイトからYoutube検索ができる。
バリデーションはフロントエンドとサーバーサイド両方でかける
なぜか?
サーバーサイドでバリデーションを設定しないとターミナルからhttpリクエストを飛ばした際に、メールのところにメールじゃないものが入ったり、文字数が足りないままリクエスト飛んじゃったりする対策として
なぜフロントエンドにもバリデーションをつけるのか?
単純にユーザーの見やすさの問題。UI的に
day3 10/26
学習2日空いてしまった、実務の忙しいタイミングとかもちろん波はあるので、夜の仕事終わりで時間決めて勉強していく!
CSSパート
知らなかったことまとめ
普段使う機会少なそうだが知っておくと便利そう。
/* 1文字目だけスタイルを当てる */
h2:first-letter {
font-size: 50px;
}
/* 1行目だけ指定してスタイルを当てる */
p::first-line {
color: purple;
}
day4 10/28
JavaScript編突入
今の1番の課題、コントロールしている感を得るためにも時間をかけて理解する。
データ種類
プリミティブ型・・・JavaScriptに最初から装備されている型
Number
String
Boolean
Null
Undefined
Number
JavaScriptには数値型は一つしかない
正・負、整数・不動小点数
%剰余演算 偶数なのか奇数なのかの演算で使用
** 冪演算
9 % 2
1
3 ** 3
= 27
3の3乗の計算
NaN
not a number
数字じゃない、numberの仲間
NaNとの演算は常にNaNになる。
0/0
= NaN
NaN / NaN
= Nan
200 + 0/0
= NaN
いつ使うのか?
フォームなどでユーザーが入力された数値、受け取った数値がちゃんと数字かの判定、不正入力数値の検出。
外部APIなどから受け取った数値が数字かの判定で使用される。
変数
値に名前をつけて管理するラベルのようなもの、後で参照できる。あとから違う値に変えることもできる。
let year = 1985;
let hoge = 5;
let hogehoge = 1;
let total = hoge + hogehoge;
<!-- 足し算 -->
let score = 0;
score +=1;
= 1
score++;
2
<!-- 引き算 -->
score--;
1
変数宣言
const・・・定数、上書き不可、変更しない値に使用する。
var・・・昔はこれしかなかったが今はあんまり使わない。
let・・・上書き可能、値を変更する必要がある場合に使用。
Boolean
trueかfalseかの判定
変数の中に格納したデータ型はあとから変えられる。けど、、、あんまり使う機会ない?
TypeScriptの場合はデータ型に制限があるが、バニラJavaScriptの場合はそれがないので注意。
変数の命名規則
絶対遵守規則
<!-- NG -->
<!-- 空白が入るのはNG -->
let hello world
<!-- 数字で始めるのはNG -->
let 123hello
開発者のための規則
キャメルケースにして書く、一般的に多く使用されている。
わかりやすい変数名にする。別の人が見てわからない変数名は使用しない。
<!-- 単語の先頭文字を大文字にする -->
let currentYear
<!-- NG -->
<!-- 何の数字? -->
let y = 1995;
<!-- Booleanの変数名の先頭にはis,hasをつける。 -->
let isLogginedInUser
day4 10/28 part2
String型
文字の並びを表すデータ型テキストを表現する、"",''で囲む。
※""と''を混同しない、どちらかに必ず統一する!
let userName = "山田";
String型はindexされている
const cat = "string cat";
cat[0]
<!-- 結果はs
変数catの0文字目 -->
<!-- 文字数を数える -->
cat.length
toUpperCase
let hello = 'hello world';
const upperHello = hello.toUpperCase();
upperHello;
<!-- 結果 -->
HELLO WORLD
<!-- メソッドは組み合わせられる -->
let greeting = ' hello world ';
const trimGreeting = greeting.trim().toUpperCase();
<!-- 結果 -->
'HELLO WORLD'
メソッドを連結することをメソッドチェインという。よく使用する。
replace正規表現、文字列を置き換えることができる。
const hello = "hello world";
hello.replace("hello", "goodnight")
<!-- 結果 -->
'goodnight world'
テンプレートリテラル
バックチックで囲むと文字列と変数・式等を同時に使用できる。
let product = 'チョコレート'
let price = 100;
let quantity = 4;
`${product}を${quantity}個買って${price}円だった。`
NullとUndefined
Undefinedはわからない。定義されていない状態。
Nullは意図的に値がない。無いことを意図的に宣言する。
<!-- ユーザーログイン前 -->
let loggedInUser = null;
<!-- ユーザーがログインした -->
loggedInUser = 'tom';
day5 11/24
基礎的な部分だが等価についての再理解
比較演算子
// 比較演算子
const math = 10 === 10;
// console.log(math);
// == 等価
// 値が等しいかチェックするが、型が等しいかはチェックしない
const math2 = null == undefined;
// console.log(math2); // trueになる
const math3 = 1 !== 1;
// console.log(math3);
// === 厳密な等価
// 比較の際は基本===を使用する。
論理演算子
and
index.OF("7") !== -1 は、文字列の中に7が存在する場合trueになる式
7が存在すれば文字列の何番目かを返す=true
7が存在しなければ-1を返す
今回は-1ではないの判定なので-1でなければtrueになる。
const mystery = 'Prompt7';
if(mystery[0] === 'P' && mystery.length > 5 && mystery.indexOf('7') !== -1){
console.log("正解!");
}
or
どちらかがtrueならtrueになる式
// or ||
// 片方の式がtrueならtrueと言える
const age = 18;
// 0歳以上5歳未満 or 65歳以上
if ((age >= 0 && age < 5) || age >= 65) {
console.log("無料");
} else if (age >= 5 && age < 10) {
console.log("子供料金1000円");
} else if (age >= 10 && age < 65) {
console.log("大人料金5000円");
} else {
console.log("無効な年齢です");
}
NOT演算子
if文とswitch文どちらがいいかはプロジェクトによりけり。
明らかにif else.......でネスト深かったり多い場合はswicth文が有効。
// NOT演算子 !
// trueとfalseの値を判定させる
// falsyな場合、trueに変換、truthな場合、falsyに変換
let userName = prompt("ユーザー名を入力してね");
// userNameが"""空文字でfalsyなので!で反転してtruthyになってif文の中身が実行される
if (!userName) {
userName = prompt("問題が起きました。ユーザー名を入力してね。");
}
SWITCH文
// SWITCH文
// ケースが実行されるとその下まで実行されるbreakで逃げないとずっと実行される
// switch文の特定を活かして、6,7が一緒の出力をしたいならbreakを入れずに7にスキップさせることもできる
const day = 7;
switch (day) {
case 1:
console.log("月曜日");
break;
case 2:
console.log("火曜日");
break;
case 3:
console.log("水曜日");
break;
case 4:
console.log("木曜日");
break;
case 5:
console.log("金曜日");
break;
case 6:
case 7:
console.log("週末");
break;
default:
console.log("無効な数字です");
}
day6 11/26
配列
配列とは、順序を持った値のコレクション
push - 末尾に追加
pop - 末尾を取り除く
shift - 先頭を取り除く
unshift - 先頭に追加
// 配列の基本
let students = ["smith", "paul", "john", "teresa"];
console.log(students);
students[0] = "dave";
// pushメソッドを使用して配列の最後に要素を追加する
students.push("ringo", "ando");
console.log(students);
// popメソッドを使用して配列の最後の要素を取り除く
const person = students.pop();
console.log(person);
console.log(students);
// shiftメソッドを使用して配列の先頭の要素を取り除く
const person1 = students.shift();
console.log(person1);
// unshiftメソッドを使用して配列の先頭に要素を追加する
students.unshift("VIP");
console.log(students);
// include()あるか?
console.log(students.includes("smith"));
// indexOf()何番目にあるか・
console.log(students.indexOf("smith"));
// reverse()要素が逆順になる 注意:元々の配列が変更される。不変性が破壊される、本当は新しい配列を作った方がいい。
students.reverse();
console.log(students);
// slice()メソッド 不変性は保たれる、別の配列として切り取る、あんまり使わない
console.log(beatles.slice(1));
// splice()メソッド 元の配列を変更する、不変性が破壊される。splice(何番目から, 何個消すか)
console.log(beatles.splice(0, 2));
console.log(beatles);
// splice(何番目から, 何個消すか, 何を追加するか)
console.log(students.splice(1, 0, "secondary"));
console.log(students);
// sort()メソッド 並び替えができる、本質は関数の中で自分で並び順を決めて使う
let scores = [1, 49, 89, 100, 90, 0];
console.log(scores.sort());
配列の等価性
これはReactでも大事な要素なので重要。
中身じゃなくて、同じ配列を参照しているかどうかが大事。
// 配列の等価性は中身じゃなくて、全く同じ配列を参照しているかどうかを判定している
let numbers = [1, 2, 3, 4, 5];
let numbersCopy = numbers;
console.log(numbers === numbersCopy); // true 同じ配列
配列のネスト
今後めちゃくちゃ使う、オブジェクトも。
アクセスの仕方も冷静になって考えればシンプル。
// 配列のネスト
const board = [
["0", null, "x"],
["0", null, "これだよ!"],
["0", null, "x"],
["0", null, "x"],
["0", null, "x"],
["0", null, "x"],
];
// 配列の1番目と2番目にアクセス
console.log(board[1][2]); // これだよ!
day7 11/27
オブジェクト
オブジェクトとは、プロパティの集合体キーと値のペア、
キーを使用してオブジェクトにアクセスできる。順序はない
// オブジェクト
// プロパティの集合体、キーと値のペア、インデックスではなく、キーを使ってアクセスする。
// データにラベリングをできる、順序はない
// オブジェクトリテラル {}で囲んでkeyとvalueのペアのこと
const person = {
firstName: "taro",
lastName: "yamada",
};
// オブジェクトのキーはStringに変換される
console.log(person.firstName); // personのkey:firstNameにアクセスする
console.log(person["firstName"]);
const restaurant = {
name: "Ichiran Ramen",
address: `${Math.floor(Math.random() * 100) + 1} Johnson Ave`,
city: "Brooklyn",
state: "NY",
zipcode: "11206",
};
const fullAddress = `${restaurant.address}${restaurant.city}${restaurant.state}${restaurant.zipcode}`;
console.log(fullAddress);
ループ処理
処理を繰り返すためにある。
- 10回繰り返す
- 配列の中の全数字の和を求める
ループの種類
- for
- while
- for...of
- for...in
forループ
// forループ
// for(初期値; 条件式; 増減式 )
// 初期値iを1として、iが10以下の間はiに+1処理を繰り返す
for (let i = 1; i <= 10; i++) {
console.log(`出力結果は${i}です。`);
}
forループのネスト
配列の中に配列が入っているときなどその中身を取り出してループさせたい時に有効。
// ループのネスト
for (let i = 0; i <= 10; i++) {
console.log(`iは${i}`);
for (let j = 1; j < 4; j++) {
console.log(` jは${j}`);
}
}
const student = [
["伊藤", "田中", "前田"],
["明智", "豊富", "卑弥呼"],
["伊能", "中大兄皇子", "綱吉"],
];
for (let i = 0; i < student.length; i++) {
// まず配列を変数に格納する
const row = student[i];
console.log(`${i + 1}行目`);
// 変数に格納した配列をさらにrowの長さ分ループさせる
for (let j = 0; j < row.length; j++) {
console.log(row[j]);
}
}
whileループ
終了のタイミングがわからない、ループ回数が不明な場合のループに有効。
ユーザーのパスワード認証とか、ゲームの勝敗がつくまで続けるとか。
// whileループ
// 更新式を書いていないと簡単に無限ループに陥るので注意
let num = 0;
while (num < 10) {
console.log(num);
num++;
}
// パスワード一致するまでループ
const SECRET = "secret";
let guest = ""; // 初期値は空文字
while (SECRET !== guest) {
guest = prompt("パスワードを入力してください。");
}
guest = alert("認証成功");
breakでループを抜ける
parseInt部分とか理解浅かったので理解するためのいい機会になった。
あくまで受け取った変数の値は変えずにwhileで比較するためにparaseIntで整数変換した上でtagetNumと比較をする。
whileでもforでも、breakを使用すると、その条件に当てはまるときに処理を抜けることができる。
// breakでループを抜けることができる
let input = prompt("何か入力");
while (true) {
input = prompt(input);
// 入力された値がquitなら終了
if (input === "quit") {
break;
}
}
while文を使用したクイズゲーム
// クイズゲーム
let maximum = parseInt(prompt("好きな数字を入力してね。"));
// 数字が入るまで繰り返し入力を求める
while (!maximum) {
maximum = parseInt(prompt("エラーです。有効な数字を入力してください。"));
}
const targetNum = Math.floor(Math.random() * maximum) + 1;
console.log(targetNum);
let guess = parseInt(prompt("1つ数字を決めました。数字を当ててみてね。"));
let count = 1;
// guessで受け取った文字列を整数に変換してランダムに生成されるtargetNumと比較
// 数値で比較するために必要、これがないと永遠に終わらない
// parseInt(guess)では、受け取ったguessを整数にした上で比較をするので、guessの値自体を変えるものではない。
while (parseInt(guess) !== targetNum) {
// ユーザーがqを入力するとループを抜ける
// guessは文字列の入力も受け取る(ここではparaIntしてないのでこれは成り立つ)
if (guess === "q") break;
count++;
if (guess > targetNum) {
guess = prompt("その値よりは小さいです!");
} else {
guess = prompt("その値よりは大きいです!");
}
}
if (guess === "q") {
alert("終了します。");
} else {
alert(`正解!🎉おめでとう!!${count}回で正解しました!`);
}
for of
for文よりもシンプルに配列の中身を取り出して使用することができる。
// 対象の配列と一個一個の名前を用意するだけで全て取り出せる
for (let subreddit of subreddits) {
console.log(subreddit);
}
// stringも列挙可能なオブジェクトのため1文字づつ取り出せる
for (let char of "helloWorld") {
console.log(char);
}
簡単なtodoアプリ
while文とif文を使用した簡単なtodoアプリ。
配列から要素を削除する際のsplice()メソッドの部分で少しつまづいた、
splice()の戻り値は配列なので、戻ってきた配列(切り取った配列)の中身にアクセスする必要がある。
let input = prompt("コマンドを入力してください。(new,list,delete,quit)");
const todos = ["水やりをする", "種まきをする"];
while (input !== "quit" && input !== "q") {
// list
if (input === "list") {
console.log("-----------");
for (let i = 0; i < todos.length; i++) {
console.log(`${i}:${todos[i]}`);
}
console.log("-----------");
// new
} else if (input === "new") {
const newTodo = prompt("新しいtodoを入力してください。");
todos.push(newTodo);
console.log(`${newTodo}を追加しました。`);
// delete
} else if (input === "delete") {
const index = parseInt(prompt("削除したいindexを入力"));
// indexに入力された値がNaNでないかの判定
if (!Number.isNaN(index)) {
const deleted = todos.splice(index, 1); // 入力された数値の位置から1個削除 splice(削除する位置, 何個削除するか)
// splice()は必ず配列として返すから、その中の唯一の中身を取り出すためにdeleted[0]としている。
console.log(`${deleted[0]}を削除しました。`);
} else {
console.log("有効なindexを入力してください。");
}
}
input = prompt("コマンドを入力してください。(new,list,delete,quit)");
}
console.log("アプリを終了します。");
関数
- 関数とは、再利用可能なコードをまとめたもの。
- まとまったコードを定義して、あとで実行できる
- いろんな場面で使用可能
定義して、実行する。
基本的な書き方
// 関数(function)
// JavaScriptのホイストで変数や関数の宣言をコードの先頭に移動させるので定義した関数の上で呼び出しても実行される
// ただ基本的には関数を上で定義して、下で実行するのが良さげ
singSong();
// 関数を定義
function singSong() {
console.log("あああああ");
console.log("Whoooooooooooooo");
console.log("Uhooooooooooo");
}
// 定義した関数を実行
singSong();
引数
引数(ARGUMENTS)喧嘩の意味らしい。
関数の入力値のようなもので、引数を渡して実行することで関数内の値を変更することができる。
// 引数
// 関数の入力値のようなもの
// 関数は機械のようなもの、実行すれば動く
// greet(パラメーター)
function greet(firstName, lastName) {
console.log(`Hello! ${firstName} ${lastName}`);
}
// // 引数
greet("Tommy", "muscle");
// 引数を渡さないと、そこにはundefinedが返ってくる
greet("Tommy");
greet();
複数の引数
引数は1つだけじゃなく複数の値を渡すことができる。
// 複数の引数
function greet(firstName, lastName) {
console.log(`Hello! ${firstName} ${lastName[0]}さん`);
}
greet("MukiMuki", "muscle");
// strで受け取った値を第二引数の数分連結させて出力する関数
function repeat(str, count) {
let result = "";
for (let i = 0; i < count; i++) {
result += str;
}
console.log(result);
}
// hello!!の文字列を3回結合
repeat("hello!!", 3);
returnの性質
- returnが実行されるとそこで処理は終了する
- returnが返せる値は一つのみ
// returnを使うと関数から値を返すことができる、returnした時点でその関数の実行が終わる。
function add(x, y) {
if (typeof x !== "number" || typeof y !== "number") {
return false;
}
// if文の中身がtrueになって中の処理が実行されると、その時点でreturn false が返されて処理は終わり
// そうじゃないならif文を通り越して下の処理が実行されて終わり。つまりreturnが返せる値は一つのみ。
// returnが実行されると処理は終わり
return x + y;
}
let total = add(5, 8);
console.log(total);
スコープ
関数の中で定義した変数は基本関数の外では参照できない。
let deadlyAnimal = "ヒョウモンダコ";
function handleAnimal() {
let deadlyAnimal = "カサゴ";
console.log(deadlyAnimal);
}
handleAnimal(); // カサゴ
console.log(deadlyAnimal); // ヒョウモンダコ
// 関数の外では影響がないのでヒョウモンダコが出力される
// レキシカルスコープはコードのどこで定義したかで決まる、実行時ではない
let x = "ああああ";
function hoge() {
console.log(x);
}
function moge() {
let x = "かきくけこ";
hoge();
}
hoge(); // ああああ
moge(); // ああああ
関数式
// 関数式
// 関数を変数に格納できる。呼び出す時も変数として呼び出せる。
const add = function (x, y) {
return x + y;
};
console.log(add(3, 6));
const square = function (num) {
return num * num;
};
console.log(square(8));
高階関数
関数を引数として受け取る。戻り値に関数を指定する。
めちゃくちゃよく使う処理。
// 高階関数、関数とやり取りをする関数。
// 引数として関数を受け取る、戻り値に関数を指定する
function callTwice(func) {
func();
func();
}
function callTenTimes(f) {
for (let i = 0; i < 10; i++) {
f();
}
}
function rollDie() {
const roll = Math.floor(Math.random() * 6) + 1;
console.log(roll);
}
// 実行しない!!!渡すだけ!
callTwice(rollDie);
callTenTimes(rollDie);
day8 11/29
メソッド
オブジェクトの中にキーとして関数を定義することができる。
※thisが参照するのは、必ずしもそのオブジェクト内であるとは限らない。
// メソッド:オブジェクトのキーに関数を定義できる。
const myMath = {
PI: 3.14,
square: function (num) {
return num * num;
},
cube: function (num) {
return num * num * num;
},
};
console.log(myMath.square);
// 省略形
const myMath2 = {
PI: 3.14,
square(num) {
return num * num;
},
cube(num) {
return num * num * num;
},
};
const square = {
area(side) {
return side * side;
},
perimeter(side) {
return side * 4;
},
};
console.log(square.area(10));
console.log(square.perimeter(10));
thisについて
thisの値はthisを使っている関数が、どのように呼ばれたか。に依存する。
// this
// このオブジェクト内が必ず参照されるわけではない!
const cat = {
name: "タマ",
color: "gray",
breed: "アメショ",
cry() {
// console.log(name); // 出力されない🙅♀️
console.log(this);
console.log(`${this.name}がにゃーと鳴く`); // 出力される🙆
},
};
// .の左側にあるものがthisになる
const cry2 = cat.cry;
try & catch
プログラムの実行を継続的に可能にする。
try-catchを使用することで、エラーが起きた場合はcatchの中の処理が実行される。
エラーの内容も引数に取って確認することもできる。errorなどの名前にしたり。
// エラーが起きた場合の処理、エラーが起きると処理がそこで終わってしまう。
try {
hello.tuUpperCase();
} catch {
console.log("エラー");
}
console.log("処理の続き");
// catch(e)とすることで、エラー内容を確認することもできる。
function shout(msg) {
try {
console.log(msg.toUpperCase().repeat(3));
} catch (error) {
console.log(error);
console.log("文字列を入れてね");
}
}
shout("hello");
shout(1234); // 数字が入るとcatchの処理に移る
for eachメソッド
配列のメソッド。配列の中身を1個づつ取り出して処理をすることができる。
ただ現代ではfor of の使用を躊躇するケースが少ない(IEのサ終)ので、基本的にfor ofを使用した方が良さそう、コードの処理の見通しも良い。列挙可能なオブジェクトに対しても使える。
for eachはあくまでも"配列"のメソッド。
// forEach、あくまで配列のメソッド、stringには使用できない。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 配列の中身を1個づつ取り出して処理をする
numbers.forEach(function (el) {
console.log(el);
});
// // for ofでも同じことはできるしわかりやすい?
for (let elem of numbers) {
console.log(elem);
}
const movies = [
{ title: "Amadeus", score: 99 },
{ title: "Bool", score: 80 },
{ title: "Candy", score: 79 },
{ title: "Eden", score: 59 },
];
// for each ver
movies.forEach(function (movie) {
console.log(`${movie.title} - ${movie.score} / 100`);
});
// for ofでも同じことはできる。
for (let movie of movies) {
console.log(`${movie.title} - ${movie.score} / 100`);
}
mapメソッド
元の配列があって、その中の要素にそれぞれ処理を加えて、新しい配列にマッピングして新しい配列を作成することができる。React開発において重要なメソッド。
配列内の全要素に対する一貫した変換
// mapメソッド
// for eachと違うのは、returnして処理された各々の値・要素で、新しい配列にマッピングすることができる。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const doubles = numbers.map(function (num) {
return num * 2;
});
console.log(doubles); // [2,4,6,8,10,12,14,16,18,20]
const movies = [
{ title: "Amadeus", score: 99 },
{ title: "Bool", score: 80 },
{ title: "Candy", score: 79 },
{ title: "Eden", score: 59 },
];
const newMovies = movies.map(function (movie) {
return movie.title;
});
console.log(newMovies);
アロー関数
関数を書きやすくしたもの。
暗黙的returnを使用する際は、式が1つだけの時に有効。
// 暗黙的リターンは一つの式だけの時有効。
const add = (x, y) => x + y;
console.log(add(3, 7));
const movies = [
{ title: "Amadeus", score: 99 },
{ title: "Bool", score: 80 },
{ title: "Candy", score: 79 },
{ title: "Eden", score: 59 },
];
const newMovies = movies.map((movie) => {
return `${movie.title} - ${movie.score} / 100`;
});
console.log(newMovies);
filterメソッドとmapメソッドは組み合わせ可能
filterメソッドは元の配列に変更を加えず、条件に合う要素だけを取り出して新しい配列を作成する。その後mapメソッドで、その配列の中の値だけを取り出して新しい配列を作成することができる。React開発でも使用する重要なこと。
const movies = [
{ title: "Amadeus", score: 99, year: 1999 },
{ title: "Bool", score: 82, year: 1997 },
{ title: "Candy", score: 79, year: 1980 },
{ title: "Eden", score: 100, year: 2024 },
{ title: "Youtube", score: 100, year: 1996 },
];
// mapメソッドと組み合わせも可能
// 配列moviesの中でscoreが80以上のものをfilterした新しい配列を作成し、mapメソッドでその配列のtitleだけを取り出して配列を作成する。
const goodMovie = movies
.filter((movie) => movie.score > 80 && movie.year > 1990)
.map((movie) => movie.title);
console.log(goodMovie);
const budMovie = movies.filter((movie) => {
return movie.score < 70;
});
// console.log(budMovie);
const validUserNames = (names) => {
return names.filter((name) => name.length < 10);
};
console.log(validUserNames(["taros", "tomtom", "testmessssssss", "tommy"]));
day9 11/30
reduce
特徴
- 配列の要素を1つずつ処理して、単一の値にまとめる
- その「単一の値」は、数値、配列、オブジェクトなど何でもOK
- 前回の処理結果を次の処理に活用できる
基本的な構文
array.reduce((accumulator, currentValue) => {
// 処理
return accumulator;
}, initialValue);
// accumulator: 蓄積値(前回の処理結果)
// currentValue: 現在処理している配列の要素
// initialValue: 初期値(省略可能)
const apiResponse = [
{ id: 1, status: "完了" },
{ id: 2, status: "未完了" },
{ id: 3, status: "未完了" },
{ id: 4, status: "完了" },
{ id: 5, status: "完了" },
{ id: 6, status: "完了" },
{ id: 7, status: "未完了" },
];
const displayData = apiResponse.reduce((acc, item) => {
// ステータスごとにグループ化
acc[item.status] = acc[item.status] || [];
acc[item.status].push(item);
return acc;
}, {});
// displayData の中身
// {
// "完了": [
// { id: 1, status: "完了" },
// { id: 4, status: "完了" },
// { id: 5, status: "完了" },
// { id: 6, status: "完了" }
// ],
// "未完了": [
// { id: 2, status: "未完了" },
// { id: 3, status: "未完了" },
// { id: 7, status: "未完了" }
// ]
// }
関数の種類におけるthisのスコープの決まり方
アロー関数と通常関数では、thisのスコープの決まり方が違う。
- 通常の関数は、呼び出し時に新しいthisバインディングを作成する
- アロー関数は自身のthisバインディングを持たない。代わりに、定義された時点での外側のスコープのthisを維持する
const user = {
name: "田中",
// メソッドの定義
greet: function () {
// setTimeout のコールバックで通常の関数を使用
setTimeout(function () {
console.log(`こんにちは、${this.name}です`);
// this.name は undefined になる
// なぜなら、この function() のスコープでの this はグローバルを指すため
}, 1000);
},
// 別のメソッド - こちらは正しく動作する例
greetCorrect: function () {
// アロー関数を使用
setTimeout(() => {
console.log(`こんにちは、${this.name}です`);
// "こんにちは、田中です" と出力される
// アロー関数は親スコープの this を維持するため
}, 1000);
},
};
デフォルト引数の設定
引数に値が入ってこない場合でも動作するように、デフォルトの引数を設定することができる。
numSize = 6
のように = で結ぶだけでOK,
順番には注意。
// デフォルト引数の設定
function rollDie(numSize = 6) {
return Math.floor(Math.random() * numSize + 1);
}
console.log(rollDie()); // 引数を設定しなかった場合、デフォルト値の6が適用される
const greed = (msg = "こんにちわ", person = "誰か") => {
console.log(`${msg}、${person}さん`);
};
// 飛ばしたい時はundefinedを使用する
console.log(greed(undefined, "田中"));
スプレッド構文
列挙可能なオブジェクトなら展開可能、React開発においてめちゃくちゃ重要な概念、よく使う。
配列やオブジェクトを直接編集するのではなく、コピーを作成してプロパティを追加できるので、イミュータビリティ(不変性)が保たれる。
// スプレッド構文
// 列挙可能なオブジェクトなら展開可能
const nums = [13, 4, 5, 6, 9, 56];
console.log(Math.max(...nums)); // 出力:中に入るのはnumsの配列の値、配列を展開できる。
console.log(...nums); // 出力:13 4 5 6 9 56
const str = "Hello";
console.log(...str); // H e l l o 1文字づつ展開される
// 配列リテラル
const num1 = [1, 2, 3, 4, 5, 6];
const num2 = [7, 8, 9, 10, 11, 12];
const newArray = [...num1, 100, 101, 102, 103, ...num2]; // 新しい配列を合体して作成できる
console.log(newArray);
// 元の配列は変更せず、新しく作成される配列は別の配列として作成される
const num1Copy = [...num1]; // 1,2,3,4,5,6と中身は同じ配列だが、、、
console.log(num1 === num1Copy); // falseになる
// stringを展開して一文字づつを値にした配列を作成することも可能
const string = "あいうえお";
const array = [...string];
console.log(array); // あ、い、う、え、お
// オブジェクトリテラルの場合
const feline = {
legs: 4,
family: "猫科",
};
const canine = {
family: "犬科",
bark: true,
};
const cat = { ...feline, color: "black" };
// console.log(cat);
// 合体可能だが、共通のプロパティのfamilyは後から展開した方の値に上書きされる。。。
const catDog = { ...feline, ...canine };
// console.log(catDog);
// 例)ユーザーが入力したデータを受け取る
const formData = {
email: "xxx@gmail.com",
password: "secret",
username: "John",
};
// 元のオブジェクトformDataを直接更新するのではなく、新しいオブジェクトuserとして、formDataをコピーして、その後にプロパティを追加していく。
const user = { ...formData, id: 123, isVerified: false };
console.log(user);
console.log(formData);
レスト構文
レスト(残り物)の意味。スプレッド構文に似ているが、
可変長の引数を扱う場合に有効。
引数の残りを全部まとめてくれる
// レスト構文 残余引数、スプレッド構文に似ているが違うもの
function sum() {
return arguments.reduce((total, num) => total + num);
}
// sum(1, 2, 3);
function sum(...nums) {
console.log(nums);
return nums.reduce((total, num) => total + num);
}
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 残りのものに引数が入る
function runResult(gold, silver, ...rest) {
console.log(`金: ${gold}`);
console.log(`銀: ${silver}`);
console.log(`その他: ${rest}`);
}
// 第一引数に"田中くん",第二引数に"マイケル"それ以降の引数はすべてrestの引数に配列として入る。
runResult(
"田中くん",
"マイケル",
"Johnくん",
"smithさん",
"エミリー",
"ミッシェル"
);
function calculateTotal(shopName, date, ...prices) {
console.log(shopName); // 'スーパー' (string)
console.log(date); // '2024-01-01' (string)
console.log(prices); // [200, 300, 500, 1000] (number[]配列)
// prices配列に対してreduceを使って合計を計算
const total = prices.reduce((sum, price) => sum + price, 0);
// 1周目: sum(0) + price(200) = 200
// 2周目: sum(200) + price(300) = 500
// 3周目: sum(500) + price(500) = 1000
// 4周目: sum(1000) + price(1000) = 2000
return `${date}の${shopName}での合計金額は${total}円です。`;
分割代入
分割代入で配列の中身を変数として定義するのが簡単になる。
特にオブジェクトの分割代入はReactでも頻繁に使用するので重要な概念。
// 分割代入
const scores = [12345, 34234, 4343, 344343, 99999, 34343];
// 分割代入を使用すると、変数の順番に配列の順が入っていく。
const [gold, silver, bronze] = scores;
console.log(gold);
// レスト構文を使用すると残りの値を格納することもできる
const [golden, ...rest] = scores;
console.log(...rest);
// オブジェクトの分割代入:頻度高め、使用したい情報だけを取り出して使用することが多い。
// オブジェクトから個別のプロパティを変数に割り当てることができる
// 基本プロパティ名と一緒、変数名を指定することもできる
const user = {
email: "string@gmail.com",
password: "string0000",
firstName: "taro",
lastName: "yamada",
born: 1999,
died: 2100,
city: "NewYork",
state: "California",
};
const user2 = {
email: "string@gmail.com",
password: "string0000",
firstName: "taro",
lastName: "yamada",
born: 1999,
city: "NewYork",
state: "California",
};
const { firstName, lastName, email } = user;
// :の後に変数名を指定すると変数の名前も変更することができる
const { born: birthYear } = user;
console.log(birthYear); // 1999
// 変数の初期値の設定もできる
const { died = "N/A" } = user2;
console.log(died);
関数パラメーターの分割代入
関数に渡ってくる、パラメーターの段階で分割代入をすることができる。めっちゃ便利で楽にかける。Reactでめちゃ使う。
// 関数パラメーターの分割代入
const user = {
email: "string@gmail.com",
password: "string0000",
firstName: "taro",
lastName: "yamada",
born: 1999,
died: 2100,
city: "NewYork",
state: "California",
};
const user2 = {
email: "string@gmail.com",
password: "string0000",
firstName: "John",
lastName: "Smith",
born: 1999,
city: "NewYork",
state: "California",
};
// 引数として渡ってきたuserオブジェクトは、パラメーターの段階で、分割代入される
// Reactで頻繁に使用
function fullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(fullName(user2));
const movies = [
{ title: "Amadeus", score: 99 },
{ title: "Bool", score: 80 },
{ title: "Candy", score: 79 },
{ title: "Eden", score: 59 },
{ title: "titan", score: 92 },
];
// コールバック関数でも分割代入を使用可能 scoreプロパティをそのまま使用している
const hightMovies = movies.filter(({ score }) => score >= 90);
console.log(hightMovies);
// map関数を使用
const review = movies.map(({ title, score }) => {
return `${title}は${score}点です。`;
});
console.log(review);
day10 12/1
DOM操作の基本
IDやタグ、クラス名で要素を取得すると、HTMLコレクションという配列っぽいものを返してくれる。
HTMLコレクション(HTMLCollection):DOMの変更をリアルタイムに反映する特別なオブジェクト
だが現代では、querySelectorやquerySelectorAllを使用する方が一般的。
// HTMLからIDを取得する
const banner = document.getElementById("toc");
// HTMLからタグをすべて取得する
const allImg = document.getElementsByTagName("div");
// for-ofでループを回すこともできる。
for (let img of allImg) {
img.src =
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Partridge_Silkie_hen.jpg/900px-Partridge_Silkie_hen.jpg";
}
// HTMLからクラス名をすべて取得する
const classes = document.getElementsByClassName("square");
for (let img of classes) {
img.src =
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Partridge_Silkie_hen.jpg/900px-Partridge_Silkie_hen.jpg";
}
現代は要素の取得はquerySelectorやquerySelectorAllを使用する方が一般的。
// 最初に見つけた要素を一つだけ返す、属性も使用できる。aタグのtitle属性とか。
const selector = document.querySelector("a[title='ヒツジ']");
// 当てはまる全ての要素を取得できる
// aタグのlink要素だけを取得して表示する
const links = document.querySelectorAll("p a");
for (let link of links) {
console.log(link.href);
}
console.log(links);
innerText,textContent,innerHTMLの違い
innerText
- 人間が見えるテキストのみを取得
- CSSで非表示の要素は無視
- CSSのスタイリングを考慮(例:display: noneの要素は含まない)
- パフォーマンスに若干の影響あり(レンダリングが必要なため)
textContent
- スクリプトタグやスタイルタグの中身も含めて、全てのテキストを取得
- CSSで非表示になっている要素も取得
- 改行やスペースをそのまま保持
innerHTML
- HTML要素を含めて取得・設定ができる
ただテキストが欲しい → textContent
画面に見えてるものだけ欲しい → innerText
HTMLタグも含めて全部欲しい → innerHTML
// // 取得した要素の操作。コンセプトを理解する。
const h1 = document.querySelector("h1");
// textContentはnode内の全てのコンテンツを取得する
// innerTextはページ上で見えてるものを取得する。
const allLink = document.querySelectorAll("a");
for (let link of allLink) {
link.innerText = "私はリンクです!!!";
}
// p要素の中の全てのHTML要素が取得できる。html要素を含めて更新
const head = (document.querySelector("h1").innerHTML =
"<b>ううううわおお!!!!</b>");
console.log(head);
day11 12/2
属性について
attribute = 属性の意味
属性を取得して変更をすることができる。
// 属性操作について。
// 取得する形式が違うかったりするので注意が必要。
// getAttribute
const firstLink = document.querySelector("a");
console.log(firstLink.getAttribute("href")); // /wiki/%E5%BA%AD 相対パス,HTMLの要素そのまま
const firstLinks = document.querySelector("a").href;
console.log(firstLinks); // http://127.0.0.1:5500/wiki/%E5%BA%AD 絶対パス,完全なURL
// setAttributeで取得した属性に対して新しい値をセットすることもできる
console.log(firstLink.setAttribute("href", "https://google.com"));
const eggImg = document.querySelector("img");
eggImg.setAttribute("src", "https://devsprouthosting.com/images/chicken.jpg");
eggImg.setAttribute("alt", "chicken");
console.log(eggImg);
CSSスタイルを変更する
// CSSスタイルを変更する
const h1 = document.querySelector("h1");
console.log(h1.style); // これでCSSを確認しても、CSSファイルで設定されたCSSは当たっていない、インラインスタイルのCSSだけが確認できる。
console.log((h1.style.color = "green")); // JSで直接スタイルを変える
console.log((h1.style.fontSize = "1.8rem")); // 必ずstringでプロパティを定義する必要がある。
const links = document.querySelectorAll("a");
for (let link of links) {
link.style.color = "pink";
link.style.textDecorationColor = "magenta";
link.style.padding = "0 2em";
}
const div = document.querySelector("#container");
const divImg = document.querySelector("img");
div.style.textAlign = "center";
divImg.style.width = "150px";
divImg.style.borderRadius = "50%";
// 配列の中にcolorのプロパティの値を格納し、取得したspan要素に順番に色を当ててレインボーにする。
const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
const strings = document.querySelectorAll("span");
let i = 0;
for (let string of strings) {
string.style.color = colors[i];
i++;
}
classList
クラスの付与・削除・つけ外しをより簡単にすることができる。
- .classList.add("クラス名"); クラス付与
- .classList.remove("クラス名"); クラス削除
- .classList.toggle("クラス名"); クラスの付け外し
const h2 = document.querySelector("h2");
// クラスを追加していくことができる
h2.classList.add("pink");
h2.classList.add("border");
// クラスを外すこともできる
h2.classList.remove("border");
// クラスの付け外しをすることもできる、ハンバーガーメニューのボタンとか
// 今の状態がそのクラスがついているかどうかを判断してtoggleさせている
h2.classList.toggle("pink");
const listItem = document.querySelectorAll("li");
for (let item of listItem) {
item.classList.toggle("highlight");
}
親・子要素を取得、次・前の要素を取得する
const firstBold = document.querySelector("b");
// 親要素を辿ることができる
console.log(firstBold.parentElement);
// 子要素も同様に辿れる
console.log(firstBold.children);
const squareImg = document.querySelector(".square");
// 次の要素を取得する
console.log(squareImg.nextElementSibling); // 実際の兄弟"要素"が返ってくる
// console.log(squareImg.nextSibling); // ノードが返ってくる
// 前の要素を取得する
console.log(squareImg.previousElementSibling);
要素を作成してHTML要素に挿入する
appendChild ver
- 一つずつしか追加できない
- テキストを追加するには追加手順が必要
append ver
- 複数のものを一度に追加できる
- テキストを直接追加できる
// 要素を作成してHTMLに挿入する
// appendChild ver
const newImg = document.createElement("img");
console.log(
(newImg.src =
"https://plus.unsplash.com/premium_photo-1732736768058-42f76dc6e6e3?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw1fHx8ZW58MHx8fHx8")
);
newImg.classList.add("square");
document.body.appendChild(newImg);
// 新しいh3要素を作成して、
const newH3 = document.createElement("h3");
// h3の中身を更新して
newH3.innerText = "これは新規追加のh3です!!";
// body要素等に挿入する
document.body.appendChild(newH3);
// append() ver
// 最後に直接挿入することができる
const p = document.querySelector("p");
p.append(
"Whooooooooooooooooooooooooo",
"aaaaaaaaaaaaaaaaa",
"eeeeeeeeeeeeeeeee"
);
const newB = document.createElement("b");
// 先頭に直接追加する
newB.append("やっぴーー");
p.prepend(newB);
// 兄弟要素に追加する
const newH2 = document.createElement("h2");
newH2.append("鶏だよ");
const h1 = document.querySelector("h1");
h1.insertAdjacentElement("afterend", newH2); // (どこのポジションに, 何を追加するか)
const h3 = document.createElement("h3");
h3.innerText = "これはH3";
newH2.after(h3);
const container = document.querySelector("#container");
// for文でid:containerの中にボタン作成を100回繰り返す。
for (let i = 0; i < 100; i++) {
const newButton = document.createElement("button");
newButton.innerText = `ボタン${i + 1}`;
container.appendChild(newButton);
}
要素を削除する
remove()を使用して自分自身を削除する。
removeChild()もあるが、IE無き今は特定のユースケースがない限りはremove()で良さそう。
// remove()を使用すれば削除される自分自身だけ考えればOK
const img = document.querySelector("img");
img.remove();
ポケモン図鑑を作ってみる
JavaScriptを使用して簡易的なポケモン図鑑を作ってみる。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ポケモン図鑑</title>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<h1>ポケモン図鑑</h1>
<section id="container"></section>
<script src="app.js"></script>
</body>
</html>
#CSS
.pokemon {
display: inline-block;
text-align: center;
}
.pokemon img {
display: block;
}
const container = document.querySelector("#container");
const baseUrl =
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/";
for (let i = 1; i <= 1025; i++) {
const pokemon = document.createElement("div"); // 外側のdiv
pokemon.classList.add("pokemon");
const label = document.createElement("span"); // 番号を表示するためのlabel
label.innerText = `#${i}`; // ラベルのテキストを#1,#2,#3...のようにテキストを挿入
const newImg = document.createElement("img"); // 画像を表示するためのimg
newImg.src = `${baseUrl}${i}.png`;
// divの中に作詞したlabel,newImgを入れる
pokemon.appendChild(newImg);
pokemon.appendChild(label);
// id:containerの中に作成されたdiv要素自体を入れる
container.appendChild(pokemon);
}
DOMイベント
ユーザーの入力やアクションに反応する。
イベントの登録は基本的にaddEventlisterを使用する。この方が複数のイベントを登録できたり、第三引数でoptionを設定できたりと柔軟な対応ができるため。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JSEvent</title>
</head>
<body>
<h1>Event</h1>
<!-- htmlに直接記述することもできるが使い回ししづらいなどあるので非推奨 -->
<button onclick="console.log('クリックされました!');">Click Me</button>
<button id="v2">Click JS ver</button>
<button id="v3">Click JS v3</button>
<button id="hello">Hello</button>
<button id="goodbye">Goodbye</button>
<script type="module" src="app.js"></script>
</body>
</html>
const btn = document.querySelector("#v2");
// クリックイベントを登録する
btn.onclick = function () {
console.log("クリックされたよ!!");
};
function scream() {
console.log("入ったよ!!");
}
// このやり方だと、複数のイベントを登録できない。。。
btn.onmouseenter = scream;
btn.onclick = function () {
console.log("2個目の処理");
};
const h1 = (document.querySelector("h1").onclick = function () {
console.log("h1だよ!");
});
// addEventListener(推奨),第3引数を設定して、オプションを設定できる。1回だけの呼び出しにしたりとか。
const button = document.querySelector("#v3");
button.addEventListener(
"click",
function () {
alert("発火!!");
},
{ once: true }
);
const helloBtn = document.querySelector("#hello");
const byeBtn = document.querySelector("#goodbye");
helloBtn.addEventListener("click", function () {
console.log("hello");
});
byeBtn.addEventListener("click", function () {
console.log("goodbye");
});
イベントとthis
イベントの中のコールバック関数ではthisはその関数の設定された要素になる。
// ランダムなrgb()の値を生成するための関数
const makeRandomColor = () => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r},${g},${b})`;
};
// イベントの中のコールバック関数の中ではthisはその関数がコールバックとして設定されている要素になる。
const buttons = document.querySelectorAll("button");
for (let button of buttons) {
button.addEventListener("click", colorRize);
}
const h2s = document.querySelectorAll("h2");
for (let h2 of h2s) {
h2.addEventListener("click", colorRize);
}
function colorRize() {
this.style.backgroundColor = makeRandomColor();
this.style.color = makeRandomColor();
}
day12 12/5
イベントのバブリング
イベントが上に泡のように上がっていってしまう現象。
e.stopPropagation();のプロパティを使用すれば、これ以上イベントが伝播しないように設定できる。
#index.html
<div id="container">
クリックして隠す
<button id="changeColor">色を変える</button>
</div>
<style>
.hide {
display: none;
visibility: hidden;
}
</style>
#app.js
const button = document.querySelector("#changeColor");
const container = document.querySelector("#container");
button.addEventListener("click", function (e) {
e.stopPropagation(); // バブリングをストップするプロパティ
container.style.backgroundColor = makeRandomColor();
});
container.addEventListener("click", function () {
container.classList.add("hide");
});
const makeRandomColor = () => {
const r = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
return `rgb(${r},${g},${b})`;
};
イベントデリゲーション
子要素で発生したイベントを親要素が処理をすることができる。
// フォームのイベント
const tweetForm = document.querySelector("#tweetForm");
// 送信した時のイベント
tweetForm.addEventListener("submit", function (e) {
e.preventDefault(); // デフォルトの挙動を止める,formはデフォルトでページ遷移してしまうので止める。
// formのelementsの中のname属性のvalueを取得する
const userNameInput = tweetForm.elements.username;
const postInput = tweetForm.elements.tweet;
addPost(userNameInput.value, postInput.value);
// 送信後はinputの中身を空にする
userNameInput.value = "";
postInput.value = "";
});
// 受け取った値で新しいポストを作成する処理
const addPost = (userNameInput, postInput) => {
const newPost = document.createElement("li");
const bTag = document.createElement("b");
const tweets = document.querySelector("#tweets"); // ポストを表示する場所
bTag.append(userNameInput); // bタグの中にuserNameInputで取得した値を入れる
newPost.append(bTag); // newPostの中にbTagを挿入する
newPost.append(` - ${postInput}`); // newPostの中にpostInputで取得した値を入れる
tweets.append(newPost); // tweetsの中にnewPostを挿入する
};
// イベントデリゲーション
// 要素自身ではなく、親要素に処理を任せる
tweets.addEventListener("click", function (e) {
if (e.target.nodeName === "LI") {
e.target.remove();
} else if (e.target.nodeName === "B") {
e.target.parentElement.remove();
}
});
卓球得点表をJavaScriptで作成する
プレイヤー情報をオブジェクトで管理して関数に切り出して管理する。
const p1 = {
score: 0,
button: document.querySelector("#p1Button"),
display: document.querySelector("#p1Display"),
};
const p2 = {
score: 0,
button: document.querySelector("#p2Button"),
display: document.querySelector("#p2Display"),
};
const resetButton = document.querySelector("#reset");
const winningScoreSelect = document.querySelector("#winningScore");
let winningScore = 3;
let isGameOver = false;
function updateScores(player, opponent) {
if (!isGameOver) {
player.score += 1;
player.display.textContent = player.score;
if (player.score === winningScore) {
isGameOver = true;
player.display.classList.add("winner");
opponent.display.classList.add("loser");
player.button.disabled = true;
opponent.button.disabled = true;
}
}
}
p1.button.addEventListener("click", function () {
updateScores(p1, p2);
});
p2.button.addEventListener("click", function () {
updateScores(p2, p1);
});
// 勝利得点の変更
winningScoreSelect.addEventListener("change", function () {
winningScore = parseInt(this.value);
reset();
console.log(typeof winningScore);
});
// リセットボタン
resetButton.addEventListener("click", reset);
// 得点のリセット処理関数
function reset() {
isGameOver = false;
// p1,p2それぞれに同じ処理を加える
for (let p of [p1, p2]) {
p.score = 0;
p.display.textContent = 0;
p.display.classList.remove("winner", "loser");
p.button.disabled = false;
}
}
day13 12/8
コールスタックについて
Chromeのデバッグツールを使用してコールスタックの処理順、積み重なっていくのを確認することができる。
const multiply = (x, y) => x * y;
const square = (x) => multiply(x, x);
const isRightTriangle = (a, b, c) => square(a) + square(b) === square(c);
console.log("before!!!");
isRightTriangle(3, 4, 5);
console.log("after!!!");
JavaScriptはシングルスレッド
JavaScriptでは一度に一つの作業しかできない。
もし重い処理だったらサイト固まる???
回避策はある、コールバック関数を使用する!
なぜsetTimeout関数を使用するとsetTimeoutのカウントをしつつその先の処理も実行できるのか?
→ブラウザが処理をしてくれている。
- ブラウザはWeb APIと呼ばれるバックグラウンドで処理を実行してくれる機能(リクエスト・setTimeoutなどの処理)を提供してくれる。
- JavaScriptのコールスタックはこのWeb APIを認識すると、ブラウザに処理を依頼する
- ブラウザが処理を終えると、コールバックとしてスタックに積まれる
何がいいのか?
メインスレッドがブロックされることなく処理を続行できる
重い処理や時間のかかる処理を別で実行できる
UIの応答性を保ったまま非同期処理を実行できる
console.log("サーバーにリクエストを送信"); // 最初に実行される処理
// この処理に来た時点で、ブラウザにsetTimeoutで3秒測るように依頼だけをして次に進む、タイマーの管理はブラウザーがする
setTimeout(() => {
console.log("サーバーからレスポンスが来ました!"); // 最後に実行される処理、3秒経過後にブラウザからコールバックを実行するように依頼が来る
}, 3000);
console.log("ここがファイルの末端"); // 2番目に実行される処理
コールバック地獄、救済としてのPromise
JavaScriptはシングルスレッドなので、JavaScript自体は複数の処理を同時にできない。コールバック関数を使用すれば回避することはできる、が、コールバック関数をネストしすぎるとコードの見通しが悪くなってしまう。
現代はPromise, async/await を使用して記述する。
// コールバックがネストしまくるとコードの見通しが悪くなる。。。
setTimeout(() => {
document.body.style.background = "red";
setTimeout(() => {
document.body.style.background = "orange";
setTimeout(() => {
document.body.style.background = "green";
setTimeout(() => {
document.body.style.background = "blue";
}, 1000);
}, 1000);
}, 1000);
}, 1000);
day14 12/9
PROMISEを使用した非同期処理
PROMISE(約束)はオブジェクト
未来のある時点で値を持つ約束をするオブジェクト
未来の出来事で、成功しても失敗しても何かしらの結果をもらう。
PROMISEには3つの状態がある
- pending(待機):初期状態
- fulfiled(成功):処理が成功して完了した状態
- rejected(失敗・拒絶):処理が失敗した状態
PROMISEオブジェクトに、成功した場合のコールバック関数、
失敗した場合のコールバック関数を登録することができる。
then(それから)は前のpromiseが実行された後に実行される。
return でpromiseを返すことによって、次のthenに繋げることができる。こうすることでコードの見通しも良くなる。
// promise
const fakeRequestPromise = (url) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * 4500) + 500;
setTimeout(() => {
if (delay > 4000) {
reject("コネクションタイムアウト");
} else {
resolve(`ダミーデータ(${url})`);
}
}, delay);
});
};
fakeRequestPromise("yelp.com/page1")
// 1個目のリクエスト
.then((data) => {
console.log("成功1");
console.log(data);
return fakeRequestPromise("yelp.com/page2"); // thenの中でpromiseを返すことで、次のthenに繋げることができる
})
// 2個目のリクエスト
.then((data) => {
console.log("成功2");
console.log(data);
return fakeRequestPromise("yelp.com/page3");
})
// 3個目のリクエスト
.then((data) => {
console.log("成功3");
console.log(data);
})
// どこかで失敗した場合の処理
.catch((error) => {
console.log(error);
console.log("失敗!");
});
Promiseの作成
resolve:成功した時に呼ぶ関数
reject:失敗したときに呼ぶ関数
promiseは一度resolveもしくはrejectされるとその状態は変更できない。
もし変更できてしまうと、APIなどで取得したデータの整合性が保てない。
どの結果を信頼していいか分からなくなり、デバックも困難になってしまうため。
自分でpromiseを定義する場面は少ないが、何をしているのか、どんな処理なのかを押さえている必要がある。作るよりも、使うことに慣れる!
// promiseの作成
const fakeRequest = (url) => {
return new Promise((resolve, reject) => {
const rand = Math.random();
setTimeout(() => {
if (rand < 0.7) {
resolve("dummy-data"); // 成功の場合に返す値
return; // resolveの後はreturnで関数の処理を終わらせる。resolveの後にrejectを実行させないため
}
reject("コネクションタイムアウト"); // 失敗の場合に返す値
}, 1000);
});
};
fakeRequest("/hoge/hoge1")
.then((data) => {
console.log("成功", data);
})
.catch((err) => {
console.log("エラー!", err);
});
async function
非同期(async)な処理をもっとすっきりと書ける新しい構文。
キーワード async / await
async(非同期)
- 関数の前につけて、この関数は非同期な関数であることを宣言する。
- asyncな関数は必ず自動的にPromiseを返す
- 関数が値を返せば、Promiseはその値でresolveする
- 関数がエラーをthrowした場合、Promiseはそのエラーでrejectする
- return文はPromise.resolve()として扱われる
- throw文はPromise.reject()として扱われる
- このため、.then()と.catch()でハンドリングできる
// asyncをつけることでPromiseにできる
const sing = async () => {
return "らららららら"; // Promiseを返す
};
// Promiseを返すのでthen()が使える、dataの中には Promiseのresolve(成功時の値)が入ってくる
sing()
.then((data) => {
console.log("成功:", data); // 結果:成功:らららららら
})
.catch((err) => {
console.log("エラ−!!", err);
});
const login = async (username, password) => {
if (!username || !password) {
throw new Error("ユーザー名またはパスワードがありません");
}
if (password === "secret") {
return "ようこそ!!!";
}
throw new Error("パスワードが間違ってます");
};
await(待つ)
非同期なコードを同期的なコードのように書くことができる
- awaitはasync関数の中でしか使えない
- awaitはPromiseがresolveまたはrejctするまでasync関数の実行を一時的に停止する
リクエストをawaitしてその値を変数に格納するのは使用頻度かなり高い!
const fakeRequest = (url) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * 4500) + 500;
setTimeout(() => {
if (delay > 4000) {
reject("コネクションタイムアウト"); // 失敗時の処理
} else {
resolve(`ダミーデータ(${url})`); // 成功時の処理
}
}, delay);
});
};
async function makeRequest() {
try {
// この中のawaitで発生したエラー(reject)は全てcatchされます
const data1 = await fakeRequest("/hoge1"); // リクエストをawaitして変数に格納する
console.log(`data1: ${data1}`);
const data2 = await fakeRequest("/hoge2"); // リクエストをawaitして変数に格納する
console.log(`data2: ${data2}`);
} catch (e) {
// どちらかのリクエストでrejectされた場合、
// eにはrejectで渡された"コネクションタイムアウト"が入ってくる
console.log("エラー発生!!!", e);
}
}
makeRequest();
AJAX
WEBサイトが表示されている裏側でリクエストを投げて情報を取得したり送信したりする。
例)
- unsplashで下にスクロールをするとどんどん画像が読み込まれていくみたいな。
スクロールを検知して処理がされる。新しい画像を取得して要素を作って表示している。 - 検索窓の入力をすると即座にサジェストがぬるっと出てくるような
AJAXで返ってくる情報はデータそのもの。
API(アプリケーションプログラミングインターフェース)
WEBエンジニアの言うAPIは大抵WebAPIのことを指す。
世の中に公開されているAPIがたくさんある、APIの中でもエンドポイントが接点となる。
無料公開のAPIもあれば、使用料の制限があるAPIも存在する。
JSONデータについて
ただのデータだけが含まれている。
JSON形式 or XML形式
- JSON:ソフトウェア同士で情報の取りをするための共通のテキストベースのフォーマット。
JSONにはundefinedという値がない、JavaScriptのオブジェクトに似ているが違うもの。
JSONを取得して、JavaScript等(Python, JAVA)に変換して使用する。共通のデータ。
APIリクエスト
APIテストツール
- POSTMAN(ちょい重いが機能が多い)
- HOPSCOTCH(軽くて速いが機能は少なめ)
ベースとなるURLとクエリーストリングは性質が異なる
- ベースURL: https://swapi.dev
- プロトコル(https)とドメイン名
- パス: api/people/5
- リソースの場所を示す
- 階層構造を表現できる
- パスパラメータ(この例では5)を含むことができる
- クエリーストリング: ?color=green&size=large
- ? から始まる、キー=値 の形式、複数のパラメータは & で連結
- オプショナルな追加情報を渡すのに使用
クエリーパラメーターの使用用途
オプショナルな条件
フィルタリング
ソート条件
Fetch API
昔: コールバックで書いていた
現代その1:Promise/fetch
現代その2:async/await
// Promise / Fetchを使用ver.
fetch("https://swapi.dev/api/people/1/") // Promiseを返してくれる
.then((res) => {
console.log("RESOLVE!", res);
return res.json(); // JSONを呼ぶとPromiseを返してくれるからthen()で繋げる
})
.then((data) => {
console.log(data); // レスポンスのbodyが使える
return fetch("https://swapi.dev/api/people/2/");
})
.then((res) => {
console.log("2個目のRESOLVE!", res);
return res.json(); // JSONを呼ぶとPromiseを返してくれるからthen()で繋げる
})
.then((data) => {
console.log(data); // レスポンスのbodyが使える
})
// エラーの場合の処理
.catch((err) => {
console.log("エラー", err);
});
// async / awaitを使用した最終形態ver.
const LoadStarWarsPeople = async (num) => {
try {
const res = await fetch(`https://swapi.dev/api/people/${num}/`); // 指定したURLにリクエストを送信、取得した値を変数 res に格納
const data = await res.json(); // Fetchで取得したレスポンスボディをJSONとして解析、データは変数 data に格納
console.log(data); // dataの中身は、res.json()メソッドが変換してくれた、JavaScriptオブジェクト
console.log(`私の好きなキャラクターは${data.name}です。`);
console.log(`このキャラクターの身長は${data.height}cmです。`);
} catch (e) {
console.log("エラー!!", e);
}
};
LoadStarWarsPeople(5);
Axiosライブラリを使用してもっと簡単にAPIリクエストを処理する
Axiosライブラリを使用することで、JSONパースの必要がなくなってもっとスッキリ記述することができる。
- res.json()の呼び出しが不要
- レスポンスデータに直接アクセス可能
小規模プロジェクトならasync/awaitで十分かもしれないが、大規模になってくると有効かも。
const getStarWarsPerson = async (id) => {
try {
const res = await axios.get(`https://swapi.dev/api/people/${id}/`); // リクエストを送信し、レスポンスを待つ。この時点でボディを取得できている
console.log(res.data.name); // JavaScriptのオブジェクト形式なのですぐにオブジェクトの中身にアクセスできる
} catch (e) {
console.log("ERROR", e);
}
};
getStarWarsPerson(5);
getStarWarsPerson(10);
APIを使用してボタンを押すたびにジョークが追加されるアプリケーション
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Axios</title>
</head>
<body>
<h1>クリックでジョーク</h1>
<button id="button">クリック</button>
<ul id="jokes"></ul>
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
<script src="app.js"></script>
</body>
</html>
// ボタンを押すとジョークが追加されていくアプリケーション
const jokes = document.querySelector("#jokes");
const button = document.querySelector("#button");
// APIからジョークを取得するための非同期関数
const getDadJoke = async () => {
try {
// 第二引数のheadersの設定
const config = {
headers: {
Accept: "application/json",
},
};
const res = await axios.get("https://icanhazdadjoke.com", config); // 第二引数でheaderなどのoptionを設定することができる
return res.data.joke; // 取得したジョークをreturnする
} catch (e) {
console.log(e);
return "No Jokes Sorry...";
}
};
// 取得したジョークliに格納して、さらにulに格納する処理
const addNewJoke = async () => {
const jokeText = await getDadJoke(); // getDadJokeで取得したジョークを変数に格納
const newLi = document.createElement("Li"); // li要素を作成
newLi.append(jokeText); // liの中に取得したジョークテキストを挿入
jokes.append(newLi); // さらにulの中にliを格納
};
// ボタンをクリックするたびにジョークが追加される
button.addEventListener("click", addNewJoke);
TV番組を取得してその画像を表示する
const form = document.querySelector("#searchForm");
form.addEventListener("submit", async function (e) {
e.preventDefault();
const searchTermInput = form.elements.query;
const config = {
params: {
q: searchTermInput.value,
},
};
const res = await axios.get(`https://api.tvmaze.com/search/shows`, config);
makeImages(res.data);
searchTermInput.value = "";
});
const makeImages = (results) => {
for (let result of results) {
if (result.show.image) {
const img = document.createElement("IMG");
img.src = result.show.image.medium;
document.body.append(img);
}
}
};
ターミナル操作について
CUI: キャラクターユーザーインターフェース
キーボードからコマンドを使用して操作する
GUI: グラフィカルユーザーインターフェース
マウスなどを使用して視覚的に操作する
なぜターミナルを使用できた方がいいのか?
-
開発効率をあげる!
-
慣れればGUIよりも早く開発が可能になる、マウスのクリック、ドロップよりも速い
-
アクセス範囲
-
普段GUIではさわれないような領域にアクセスして操作することができる(取り扱い注意)
-
サーバーを実行したり止めたりの操作が可能になる
-
node.js,データベースを操作するのに必須!
ターミナルとは
コンピュータを操作するためのテキストベースのインターフェース
昔は端末のことを指していた。確かに和訳だと端末の意味。
例)ATMの端末がターミナルと言える
シェルとは
ターミナル上で動くプログラムのこと
例)ATM上で動いているソフトウェアがシェルと言える
BASHとは
シェルの一種、ちょっと前までMacの標準シェル
Node.jsとは?
JavaScriptのランタイム
ブラウザの外で実行できるランタイム
JavaScriptを使用してサーバーサイドのコードを書くことができる
何が作れるのか?
- Webサーバー
- コマンドラインツール
- ネイティブアプリ(VS codeとかSlackとかもNode.jsで作られている)
- ゲーム(あまり一般的ではない)
- ドローンのソフトウェア
...etc
Node.jsと通常のJavaScriptの違い
-
Node.jsにないもの
- ブラウザに関するもの。window, document, DOMのAPIは存在しない
-
Node.js特有のもの
- ブラウザに存在しないたくさんの組み込みモジュール、OS,ファイル、フォルダとのやり取りをサポートしてくれる
Node.jsでJavaScriptを実行する方法
node + JavaScriptファイル名
で記述されたJavaScriptが実行される。
for (let i = 0; i < 10; i++) {
console.log("Node.jsからこんにちは");
}
JavaScriptを記述して、ターミナルから下記コマンド実行(対象ディレクトリにいる必要あり。)
もしくは完全パスを渡す
node script.js
実行すると、ターミナルに出力される
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
Node.jsからこんにちは
npmを使用して簡単なアプリケーションを作成してみる
npmを組み合わせることで数行でアプリケーションを作成することができる。
package.jsonを使用することで、使用するnpmがdependenciesの部分に記録されていくので、node_modulesごと他人にコードを渡さなくていい(基本渡さないしGithubにもあげない)ようになる。
dependenciesは依存しているパッケージのこと。
package.jsonがあれば、開発者は【npm install】で必要なnode_modulesを一括でインストールできる。
// コマンドライン引数から入力を取得(3番目の引数)
const input = process.argv[2];
// francを使って言語コードを取得
const langCode = franc(input);
// 言語判定できなかった場合("und" = undefined)
if (langCode === "und") {
console.log("解析できません".red);
} else {
// 言語コードから言語名を取得して表示
const language = langs.where("3", langCode);
console.log(`${language.name}でしょうか?`.green);
}
Expressを理解する
ドキュメントを読んだ時にコードの意図がわかるようにする。
自分で0から書くというよりは処理を理解をする。
Expressとは?
npmのパッケージの一つ。Node.jsのためのWEBアプリ用のフレームワーク。
何ができるのか?
- リクエストを受けるサーバーの起動
- リクエストのパース(JavaScriptで扱えるように変換)
- リクエストとルーティングのマッピング
- HTTPレスポンスと関連コンテンツの作成
ルーティングについて
app.getを使用してパスに応じて処理を変えることができる。
以下の例はブラウザにresとしてテキストを表示する。
全てのパスで実行できる処理もあるが、順番に注意。
// ルーティング、パスに応じて処理を変える
app.get("/cats", function (req, res) {
res.send("にゃー!");
});
app.get("/dogs", function (req, res) {
res.send("わんわん");
});
app.get("/", function (req, res) {
res.send("ホームページ");
});
// ルーティングは定義した順番にマッチングしてくるので順番注意!、全部にマッチするワイルドカード的なもの
app.get("*", function (req, res) {
res.send("そんなパスはない!");
});
// POSTメソッドなど複数のメソッド使用可能
app.post("/cats", function (req, res) {
res.send("/catsにPOSTリクエストが来ました。");
});
Expressのルーティングにおける動的パラメータの仕組み
URLパスの中で :変数名 にすることで、動的なパラメーターを取得することができる。
その取得した値は、req.paramsオブジェクトのプロパティとして使用できる。
URLから取得したパラメーターを使用してデータベースから必要なデータを取得したり、フィルタリングをする際にとても有効。
// パターンとパラメーターを使用する
// 使用例:
// /r/gaming にアクセス → subreddit = "gaming"
app.get("/r/:subreddit", function (req, res) {
const { subreddit } = req.params;
res.send(`<h1>${subreddit}subredditのページ</h1>`);
});
// :を使用することで、パターンとパラメーターを使用する、この値からデータベースから何か取得したりできる。
// 使用例:
// /r/gaming にアクセス → subreddit = "gaming"
// ルーティングの中で超重要な仕組み
app.get("/:page/:postId", function (req, res) {
const { page, postId } = req.params;
res.send(`<h1>${page}subredditのpostIdが${postId}ページ</h1>`);
});
クエリーストリングを使用する
検索結果の表示のようなもの
URLの末尾に?キー:値 の形でデータを付加するもの。
検索、フィルタリング、ページネーションなどに使用
順序に依存しない
クエリストリングは特に検索機能やフィルタリング機能の実装において非常に重要な役割を果たす。
// クエリーストリングを使用する
app.get("/search", (req, res) => {
const { q } = req.query;
if (!q) {
res.send(`<h1>検索するものが指定されていません。</h1>`);
} else {
res.send(`<h1>「${q}」の検索結果</h1>`);
}
});
GETとPOSTのリクエストの違い
GET
- 情報の取得に使用
- データはクエリストリングで送られる(URLで見てわかる)
- 送られるデータの量に注意
POST
- データをサーバーに送信するときに使用
- 書き込み・作成・更新に使用する
- データはクエリストリングではなく、リクエストボディで送信する(URLを見てもわからない)
- どんな種類のデータでも送信可能(JSON)
フォームから送られてきた値を取得して使用する
express.urlencoded(): HTMLフォームから送信されるデータを解析
express.json(): JSON形式のデータを解析
これらのミドルウェアがないと req.body が undefined になってしまう。
フォームから送信されたデータは req.body に格納されます
分割代入で meat と qty を取り出して使用可能。
app.post("/tacos", (req, res) => {
const { meat, qty } = req.body;
res.send(`${meat}を${qty}個どうぞ`);
});
取得したreq.bodyの中身は下記
{
"meat": "meat",
"qty": "4"
}
const express = require("express");
const app = express();
// パースしてデータ形式を変更してあげる
app.use(express.urlencoded({ extended: true })); // フォームからだけ送られてきたデータをパースできる
app.use(express.json()); // JSONもパースする
// GETメソッド
app.get("/tacos", (req, res) => {
res.send("GET /tacos response");
});
// POSTメソッド
app.post("/tacos", (req, res) => {
const { meat, qty } = req.body; // 分割代入で取得、フォームから送られてきたデータをリクエストのボディからデータを取得する、パースできるとreq.bodyに入ってくる
console.log(req.body);
res.send(`${meat}を${qty}個どうぞ`);
});
// サーバー立ち上げ
app.listen(3000, () => {
console.log("ポート3000で待受中。。。");
});
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>GETとPOSTリクエスト</h1>
<h2>GET</h2>
<form action="http://localhost:3000/tacos" method="get">
<input type="text" name="meat" placeholder="meat" />
<input type="number" name="qty" placeholder="個数" />
<button>Submit</button>
</form>
<hr />
<h2>POST</h2>
<form action="http://localhost:3000/tacos" method="post">
<input type="text" name="meat" placeholder="meat" />
<input type="number" name="qty" placeholder="個数" />
<button>Submit</button>
</form>
</body>
</html>
RESTについて
REPRESENTATIONAL
STATE
TRANSFER
のこと。
分散システムにおいて、複数のソフトウェアを連携させるのに適した設計原則の一つ。
クライアントとサーバーがどのようにコミュニケーションをとり、与えられたソースに対してどうCRUD操作を行うべきかと言うガイドライン・思想のこと。
※CRUDとは:データベース管理システム(DBMS)でデータを操作する際の4つの基本的な機能
Create(作成)、Read(読み出し)、Update(更新)、 Delete(削除)
※ルーティングとは:ネットワーク上でデータを転送する際に、最適な経路を選択するプロセスのこと。
REST fulは、RESTに則って作られたもののこと。
URLパターンやメソッドの使用ルールを決めてメンテナンスもしやすく、
チーム全体でのコミュニケーションを円滑にする共通のルールのように設定する。
REST fulなコメント管理アプリを作成してみる
REST fulなルーティングを作成する
/comments をベースとする、用途に合わせてhttpメソッドを変える
GET /comments - コメント一覧を取得
POST /comments - 新しいコメントを作成
GET /comments/:id - 特定のコメントを一つ取得
PATCH /comments/:id - 特定のコメントを更新
DELETE /comments/:id - 特定のコメントを削除
コメント詳細へのリンクを取得する際に重複しない一意の値(ID)が必要になってくるので、
UUIDを使用して新規コメントに対してもuuidを使用して一意のIDを付与する。
const express = require("express");
const app = express();
const { v4: uuid } = require("uuid");
uuid();
// // パースしてデータ形式を変更してあげる
app.use(express.urlencoded({ extended: true })); // フォームからだけ送られてきたデータをパースできる
app.use(express.json()); // JSONもパースする
app.set("view engine", "ejs");
// REST fulなルーティングを作成する
// /comments をベースとする、用途に合わせてhttpメソッドを変える
// GET /comments - コメント一覧を取得
// POST /comments - 新しいコメントを作成
// GET /comments/:id - 特定のコメントを一つ取得
// PATCH /comments/:id - 特定のコメントを更新
// DELETE /comments/:id - 特定のコメントを削除
const comments = [
{ id: uuid(), username: "Dave", comment: "Happy!!" },
{ id: uuid(), username: "鈴木", comment: "幸せですか?" },
{ id: uuid(), username: "田中", comment: "ワロス" },
{ id: uuid(), username: "さんちゃん", comment: "ミラクルワンダフル!!!" },
{ id: uuid(), username: "三日月", comment: "タンバリン" },
];
// コメントを取得
app.get("/comments", (req, res) => {
res.render("comments/index", { comments }); //viewsからのファイルパスが間違っていると表示されないので注意
});
// 新規コメントを作成するルーティング
app.get("/comments/new", (req, res) => {
res.render("comments/new");
});
// コメントをPOSTで /comments へ送信し、配列に格納する
app.post("/comments", (req, res) => {
const { username, comment } = req.body; // req.bodyからusernameとcommentだけを取得
comments.push({ username, comment, id: uuid() }); // 取得したusername,commentを配列commentsにpush、本来はデータベース
res.redirect("/comments"); // commentsにリダイレクトする、何も指定しなければ302リダイレクト
});
// コメントを取得して表示する
app.get("/comments/:id", (req, res) => {
const { id } = req.params;
const comment = comments.find((c) => c.id === id); // findで一つだけ取得する
res.render("comments/show", { comment });
});
// サーバー立ち上げ
app.listen(3000, () => {
console.log("ポート3000で待受中。。。");
});
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>comments一覧</title>
</head>
<body>
<h1>comments一覧</h1>
<ul>
<% for(let c of comments) { %>
<li>
<%= c.comment %> - <%= c.username %><a href="/comments/<%= c.id %>"
>詳細</a
>
</li>
<%} %>
</ul>
<a href="/comments/new">新規コメント作成</a>
</body>
</html>
MongoDBについて
そもそもなぜデータベースが必要なのか?
- 大量のデータを効率的に扱い、保存することができる
- データの挿入・照会・更新・並び替え・絞り込みを容易にするツールを提供してくれる
- データへのアクセスを制御するセキュリティ機能を有している
- スケールする(規模の拡大)
SQLデータベースとNoSQLデータベース
SQLデータベース
リレーショナルデータベース
データを挿入する前に、テーブルのスキーマをあらかじめ定義する
テーブルの型を作ってその通りにデータを入れていく
複数のテーブル間でリレーション(紐づける・関連づける)ことができる
- MySQLなど
NoSQLデータベース
SQLを使用しない
ドキュメント型・キーバリュー型・グラフ型,etc...
スキーマレスでデータ構造が柔軟なもの
- MongoDB
- CouchDB
- etc...
MongoDBを使用する理由
- Node.jsやExpressと一緒によく使用される
- MEAN,MERN
- JavaScriptと相性がいい
- 使用者も多い
MongoDBコマンド操作
検索などで比較演算子や論理演算子などが使える
// dogsのデータベースに追加する
db.dogs.insertOne({ name: "ポチ", age: 3, breed: "corgi", catFriendly: true });
// 複数追加
db.dogs.insert([
{ name: "ハチワレ", age: 14, breed: "shiba", catFriendly: false },
{ name: "ちいかわ", age: 5, breed: "chiikawa", catFriendly: true },
]);
// catsデータベースに追加する
db.cats.insert({ name: "tama", age: 6, dogFriendly: false, breed: "fold" });
// データを探す
db.dogs.find({ breed: "corgi" });
// データの更新
db.dogs.updateOne({ name: "ポチ" }, { $set: { age: 4 } });
// dogsデータベースの中でageが5より小さい
db.dogs.find({ age: { $lt: 5 } });
Mongooseとは?
アプリでデータを操作する際は、アプリとMongoDBを直接やりとりできるようにする。
それがDriver
MongooseはNode.js用のMongoDBのObject Data Mapping(ODM)ライブラリ
ODMとは?
Object Data Mapper
MongooseのようなODMはデータベースから送られてくるデータを、JavaScriptのオブジェクトにマッピングする。
Mongooseはアプリケーションのデータをモデル化して、スキーマを定義することができる。データの検証、複雑なクエリをJavaScriptで簡単に作成できる。
スキーマとは?
データベースにおける設計図・ルールのようなもの。
できること
- データ型の定義(データの一貫性を保つ)
- バリデーションルール設定
- デフォルト値の設定
何がいいのか?
- 予測可能な形式でデータが保存できる
- データ構造が明確になる
- チーム開発の共通理解が簡単になる
MongooseのCRUD処理
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost:27017/movieApp", {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("接続完了!");
})
.catch((error) => {
console.log("接続エラー!", error);
});
// スキーマを定義する(データベースにおける設計図・ルール)
const movieSchema = new mongoose.Schema({
title: String,
year: Number,
score: Number,
rating: String,
});
// モデル(クラス)の作成
const Movie = mongoose.model("Movie", movieSchema);
// インスタンスを作成
const amadeus = new Movie({
title: "Amadeus",
year: 1986,
score: 9,
rating: "R",
});
// Mongooseに複数データを追加する
// Movie.insertMany([
// { title: "GUNDAM", year: 1978, score: 7.7, rating: "R" },
// { title: "ZGUNDAM", year: 1989, score: 5, rating: "R" },
// { title: "SEED", year: 2001, score: 6, rating: "R" },
// { title: "SEED DESTINY", year: 2004, score: 10, rating: "R" },
// { title: "Witch from Mercury", year: 2023, score: 8, rating: "PG-13" },
// ]).then((data) => {
// console.log("成功!", data);
// })
// Expressアプリでめちゃくちゃよく使う、データの検索
Movie.findById("676120b1790d14239aadd818").then((m) => console.log(m));
// データの更新
Movie.updateOne({ title: "sample" }, { year: 2200 }).then((res) =>
console.log(res)
);
// 複数データの更新
Movie.updateMany(
{ title: { $in: ["sample", "GUNDAMSEED"] } },
{ score: 100 }
).then((res) => console.log(res));
// データの削除
Movie.deleteOne({ title: "sample" }).then((msg) => console.log(msg));
// yearが1999以上のものを全て削除
Movie.deleteMany({ year: { $gte: 1999 } }).then((msg) => console.log(msg));
バリデーションについて
設定したルールに適していない値を弾くことができる。
カスタムメッセージや、enumのように、入る値をコントロールすることもできる。
実行コマンドのメモ
// MongoDBサービスの起動
brew services start mongodb-community
// MongoDBシェルの起動
mongosh
// MongoDBシェルの終了
exit
// データベース一覧表示
show dbs
// きれいに整形して表示(コレクション名は、作成したモデルの複数形小文字)
db.コレクション名.find()
例)db.products.find()
// MongoDBサービスの停止
brew services stop mongodb-community
// 実行
node product.js
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost:27017/shopApp", {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("接続完了したよ!!");
})
.catch((error) => {
console.log("接続エラー!", error);
});
// MongooseスキーマをmongooseSchemaインスタンスとして作成
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
min: [0, "priceは0より大きい値にしてください"], // カスタムエラーメッセージを出す
},
onSale: {
type: Boolean,
default: false,
},
categories: [String],
qty: {
online: {
type: Number,
default: 0,
},
inStore: {
type: Number,
default: 0,
},
},
size: {
type: String,
enum: ["S", "M", "L"],
},
});
// コレクション名はproducts Product → products となる
// Mongooseモデルの作成
const Product = mongoose.model("Product", productSchema);
// Productモデルの新しいドキュメントインスタンスを作成
const bike = new Product({
name: "ジャージ",
price: 1900,
categories: ["サイクリング"],
size: "S",
});
bike
.save()
.then((data) => {
console.log("成功!", data);
})
.catch((err) => {
console.log("エラー!!", err);
});
モデルにメソッドを追加することができる(インスタンスメソッド)
// アロー関数ではなく通常のfunctionにする。理由はthisが定義した場所のthisになってしまうから、読んでくれたインスタンスがthisになるようにする
productSchema.methods.greet = function () {
console.log("ハロー!!");
console.log(`- ${this.name}からの呼び出しです。`); // greetを呼んだインスタンスが入ってくる
};
// モデルにメソッドを追加することができる
// onSaleの値を反転させるメソッド
productSchema.methods.toggleOnSale = function () {
this.onSale = !this.onSale;
return this.save();
};
// カテゴリーの追加メソッド
productSchema.methods.addCategory = function (newCat) {
this.categories.push(newCat);
return this.save();
};
const Product = mongoose.model("Product", productSchema);
const findProduct = async () => {
const foundProduct = await Product.findOne({ name: "安全なヘルメット" });
console.log(foundProduct);
await foundProduct.toggleOnSale(); // セール中かどうかの反転
console.log(foundProduct);
await foundProduct.addCategory("アウトドア"); // カテゴリーの追加
console.log(foundProduct);
};
findProduct();
モデルにメソッドを追加することができる(staticメソッド)
// staticメソッド、(モデル自身がthisとなる)全品セール、priceも0円
productSchema.statics.fireSale = function () {
return this.updateMany({}, { onSale: true, price: 0 });
};
// 呼び出し
Product.fireSale().then((msg) => console.log(msg));
インスタンスメソッドとstaticメソッドの違い・使い分け
インスタンスメソッド
- 個別のドキュメントに対する操作
- thisは呼んだインスタンスを指す
- 「特定の商品の値段を変更する」(その商品のみに影響)
productSchema.methods.toggleOnSale = function() {
this.onSale = !this.onSale;
return this.save();
};
// 使用例
productSchema.statics.fireSale = function() {
return this.updateMany({}, { onSale: true, price: 0 }); // 全商品が対象
};
productSchema.statics.sortByPrice = function() {
return this.find({}).sort({ price: 1 }); // 全商品を価格順に並び替え
};
// 使用例
await Product.fireSale(); // 全商品が0円のセール対象に
const sortedProducts = await Product.sortByPrice(); // 全商品を価格順に取得
staticメソッド
- コレクション全体に対する操作
- thisはクラスを指す
- 「お店全体の方針を変更する」(全商品に影響)
// 使用例
await Product.fireSale(); // 全商品が0円のセール対象に
const sortedProducts = await Product.sortByPrice(); // 全商品を価格順に取得
MongooseとExpressを同時に使用するチュートリアル
ミドルウェアを使用・自作する
ミドルウェアとは
「リクエストが来てから」「レスポンスを返すまで」の間の工程で、必要な処理を追加できる仕組み。
app.useについて
app.useは全てのリクエストに対して適用される。
- リクエストが来る
- app.useで登録された順番にミドルウェアが実行される
- 最後にルートハンドラが実行される
※ ルートハンドラとは
- HTTPリクエストを受け取って処理する関数
- リクエストの最終的な目的地
- レスポンスを生成して返す役割を持つ
// 基本的なルートハンドラの構造
app.get('/products', (req, res) => { // これがルートハンドラ
res.render('products/index');
});
使用例
// 全てのリクエストでログを出力
app.use(morgan('dev'));
// 特定のパスのみに適用するミドルウェア
app.use('/admin', adminMiddleware); // /admin/*のリクエストのみに適用
// 個別のルートに対するミドルウェア
app.get('/products', authMiddleware, (req, res) => {
// ...
}); // 全てのリクエストに対してログを出力
ミドルウェアを自作する
ミドルウェア関数の中でnext()を呼ぶことで次のミドルウェアに処理を渡す(Node.jsは本質的にミドルウェアの集合なので次のミドルウェアでOK)
最後のミドルウェアまで実行された後、レスポンスが返される。
ミドルウェアのバトンリレー的なもの。
// 自作したミドルウェア①
app.use((req, res, next) => {
console.log("自作したミドルウェア!!!");
return next(); // nextを呼ばないと処理が止まってレスポンスまで行かない!
});
// 自作したミドルウェア②
app.use((req, res, next) => {
console.log("2個目のミドルウェア!!!");
return next();
});
特定のルーティングに対してミドルウェアを組み込む
app.useで全体のルーティングへのミドルウェアの設定だけでなく、特定ルートのみのミドルウェアを設置することができる。
例)パスワード認証されていないと見れないページ(ルート)
※デモのため絶対に本番環境では下記のような実装はしない!セキュリティゆるゆる
// 簡易パスワード認証ミドルウェア(!本番では絶対やっちゃだめ!)
// 関数にすることで、特定のルーティングで使用することができる
const verifyPassword = (req, res, next) => {
const { password } = req.query;
if (password === "supersecret") {
return next();
}
res.send("パスワードが必要です。");
};
...
// 認証ページ
// 第二引数に作成したミドルウェアを設定して、特定のルーティングにだけミドルウェアを設定することができる
app.get("/secret", verifyPassword, (req, res) => {
res.send("ここは秘密のページ!!突破おめでとう!");
});