JavaScriptの前提
- どの実行環境でも動くものをECMAScriptと言う
- 大文字と小文字を区別する
const name = "tuna";
// 別の変数として認識
const NAME = "tuna";
- strict mode
- 古い構文などを禁止して安全にする実行モード
- 明示的に書かない場合はすべてこれで実行される
"use strict";
1.基本文法
コメント
- 一行コメントと複数行コメントがある
// 行コメント
/*
複数行コメント
複数行コメント
*/
変数宣言
- const
- 再代入できない変数
- 初期値を指定しなければいけない
const num = 10;
// 一度に定義する場合
const a = "Java",
b = "Script";
- constは"定数"ではない
- constで定義したオブジェクトの値などは再代入できる
// オブジェクトの値は初期化後でも代入できる
const object = {
key: "値"
};
object.key = "新しい値";
- let
- 再代入可能な変数
- 初期値なしで定義可能(=undefinedになる)
// 初期値なし
let num = 10;
// 初期値あり
let count = 0;
// 再代入
count = 1;
変数宣言のルール
- 半角英字, _, $, 数字を組み合わせた名前にする
- 数字から開始しない
- 予約語を使わない
- varは使わない
値の評価
値の評価:入力した値を評価して返すこと
値の評価の確認方法
開発者ツールのconsoleを使う
consoleに入力した値は評価されて返ってくる
// 入力した値が評価され返ってくる
> 1
1
例)変数bookTitleを定義する
> const bookTitle = "Javascript Primer"; // 定義時はundefinedが返る
undefined
> bookTitle
"Javascript Primer"
※REPL機能という
HTMLからJavaScriptを読み込む
<script src="ソースのパス"></script>
で.jsファイルを読み込む
<html>
<head>
<meta charset="UTF-8">
<title>example</title>
<!-- JSファイル読み込み -->
<script src="./index.js"></script>
</head>
<body>
</body>
</html>
1; // 評価はされるがconsoleには表示されない
console.log(1); // consoleAPIで表示する
console.log(引数)
コンソールに引数の評価結果を表示する
console.log(1 + 1); // => 2
const bookTitle = "JavaScript Primer";
console.log(bookTitle); // => "JavaScript Primer"
構文エラー
SyntaxError: missing ) after argument list (at index.js:3:13)
→構文エラー、)が足りない場合
実行時エラー
ReferenceError: x is not defined
→実行中のエラー、xが定義されていない場合
データ型
前提
- JavaScriptには値の型がある
- リテラルとはデータを直接記述する構文
-
プリミティブ型とオブジェクト合わせて7種
-
プリミティブ型
- Boolean型
- Number型
- String型
- null
-
オブジェクト
- オブジェクト
- 配列
- 正規表現
-
プリミティブ型
型を調べる際は
console.log(typeof ~~)で調べる
console.log(typeof "JavaScript"); // => "string"
プリミティブ型(4つ)
真偽値リテラル(Boolean)
true;
false;
数値型(Number)
整数リテラル(4種類)
- 10進数
0, 1, 2
- 2進数
0b
+0と1の値
- 8進数
0o
+0~7の値
- 16進数
0x
+0~fの値
console.log(123); // 123
console.log(0b1111); // 15
console.log(0o10); // 8
console.log(0xffff); // 65535
浮動小数点リテラル
- 少数点を含む数値
3.1415
- 指数を含む数値
2e8
console.log(3.14); // 3.14
console.log(2e10); // 2^10
BigInt
- 数値リテラルの最大値2^53-1より大きな整数を扱う際に使う
-
数値+n
→1n
,9007199254740992n
数値リテラルのNumeric Separator
桁を見やすくするために_
で数値を分けられる
1_000_000_000_000;
文字列リテラル(String)
- 文字列リテラルは
""
か''
で囲む - 複数列の文字列は
``
で囲む(テンプレートリテラル)
"JavaScript";
`複数の
文字列は
こう入れる`;
nullリテラル
nullは「値が無い」を示す値。
※undefinedは似ているが、リテラルではなくグローバル変数
const foo = null;
console.log(foo); // null
nullなしで空の変数を参照はできない
bar
オブジェクト
- キーとバリューのセットを持つもの
- キーのことをプロパティと呼ぶ
- プロパティの参照は
ドット記法
,ブラケット記法
がある
オブジェクトの作成と参照
const obj = {
"key": "value",
"number": 1
};
console.log(obj) // => {key: 'value', number: 1}
console.log(obj.key); // => "value"
console.log(obj["number"]); // => 1
配列リテラル
配列は順序をつけて格納できるオブジェクトである
// 配列
const array = ["a", "b","c"];
console.log([2]); // => "c"
正規表現リテラル
正規表現とは
- 文字列の組み合わせを照合するためのパターンの表記のこと。
- リテラルでは
\\
で囲む
const numberRegExp = /\d+/; // 一文字以上の数字
console.log(numberRegExp.test("123")) // => true
ラッパーオブジェクト
プリミティブ型はプロパティにアクセスしようとすると自動的にラッパーオブジェクトに変換される
例)文字列strに対してstr.length
で長さを調べる
const str = "文字列";
console.log(str.length); // => 3
演算子
- 演算を行う処理を表す
- 演算を行う対象をオペランドと呼ぶ
演算子の種類
- 二項演算子
+, -, *, /, **, %
- 単項演算子
-
+1
,-1
-
++
,--
-
- 比較演算子
-
==
,===
,>, >=
など比較
-
- 論理演算子
-
&&, || ,!
などand, or, not論理演算
-
- ビット演算子
-
&, |, ^, ~
など符号付32ビット整数の演算
-
- 代入演算子
=
- 条件(三項)演算子
条件式 ? Trueのとき処理する式 : Falseのとき処理する式;
比較と論理と三項演算のみ以下まとめる
比較演算子
厳密等価 ===
同じ型、同じ値の時trueを返す
console.log(1 === 1); // true
console.log("aaa" === "aaa"); // true
console.log("aaa" === "bbb"); // false
オブジェクトは同じ参照の時tureを返す
const objA = {};
const objB = {};
console.log(objA === objA); // true
console.log(objA === objB); // false
厳密不等価 !==
異なる値または異なる方のときtureを返す
console.log(objA !== objB); // true
等価、不等価
基本的な挙動は厳密等価と同じだが、
- 暗黙的な型変換をして比較する
- nullとundefinedを比較するとtrueが返る
あまり使うべきではない。
console.log(undefined == null); // true
console.log("1" == 1); //true
論理演算子
-
AND
&&
かつ- 左辺がtureなら右辺の評価結果を返す
- 左辺がfalseなら左辺を返す
-
OR
||
または- 左辺がtrueならそのまま左辺の値を返す
- 左辺がfalseなら右辺を返す
短絡評価=すでに決まった値があれば次を評価しない仕組み
// 左辺がtrueなので、続けて右辺が評価される
true && console.log("このコンソールログは実行されます");
// 左辺がすでにfalseなので、右辺は評価されない
false && console.log("このコンソールログは実行されません");
// 左辺がすでにtrueなので、右辺は評価されない
true || console.log("このコンソールログは実行されません");
// 左辺がfalseなので、次に右辺を評価する
false || console.log("このコンソールログは実行されます");
三項演算子
条件式 ? Trueのとき処理する式 : Falseのとき処理する式;
const valueA = true ? "a" : "b"; // ?の前がtrueなので"a"
console.log(valueA);
const valueB = false ? "a" : "b"; // ?の前がfalseなので"b"
console.log(valueB);
暗黙的な型変換
- JavaScriptでは型変換が自動で起こる仕様がある
-
==
や文字列の結合
など - 意図しない挙動に繋がるので避ける対策する
- 明示的に型変換する
- 比較には
===
を使う
// 暗黙的な型変換が起こる例
console.log(1 == true); // ture
console.log(1 + "2"); // 12
明示的な型変換の方法
Boolean型への変換 Boolean()
Boolean(引数)
で引数をtrueかfalseに変換する
const text = "abc";
console.log(Boolean(text)); // ture
falsyな値
暗黙的な型変換によってfalse
扱いされる値をfalsyと呼ぶ
false
undefined
null
0
0n
NaN
""(空文字)
let x; // undefined
if (!x) {
console.log(x + "はfalsy");
}; // undefined はfalsy
より正確に真偽値を得るには===
で比較すべし
let y; // undefined
if (y === undefined) {
console.log(x + "はfalsy");
}; // undefined はfalsy
String型への変換String()
-
String(引数)
で文字列に変換 - オブジェクトに対しては使用しない
- あくまでプリミティブ型の文字列変換
const z = false;
console.log(String(z)); // "false"
String(undefined); // "undefined"
文字列→数値変換
Number()
Number.parseInt()
Number.parseFloat()
-// ユーザからの入力を受け取る
const input = window.prompt("数値を入力して下さい", "42");
console.log(input); // "42"または"入力文字"
// 数値に変換
const num = Number(input);
console.log(typeof num, num); // Number 42
parseInt()
parseFloat()
は
数字以外(undefinedも)を渡されるとNaNを返す
console.log(Number.parseInt("20px")) // 20
console.log(Number.parseInt("aaa")) // NaN
NaN(Not a Number)とは
Number型と互換性がない値をNumberに変換すると発生する'値'(※Number型)
何と演算しても結果がNaNになってしまうので混入を防がなければならない
NaNの判定にはNumber.isNaN()
を使う
関数宣言
基本の書き方
function 関数名(仮引数1, 仮引数2...) { 処理 }
// 入力を倍にする関数
function double(num) {
num = Number(num);
return num * 2;
}
// windowで入力 → console出力
const input = window.prompt("数値を入力して下さい");
console.log(double(input));
-
return
関数の実行結果を返す - 省略か処理を書かない場合はundefinedを返す
- 引数が少ないときは余った仮引数にundefinedが入る
function func1() {}
console.log(func1()); // undefined
function func2() {
return;
}
console.log(func2()) // undefined
引数が余る場合
function argumentsToArray(x, y) {
return [x, y];
}
console.log(argumentsToArray(1)); // [1, undefined]
デフォルト引数
仮引数に値が与えられない時の引数
function 関数名 (仮引数=デフォルト引数) {}
// デフォルト引数
function addPrefix(text, prefix = "デフォルト:") {
return prefix + text;
}
console.log(addPrefix("aaa", "カスタム:"));
console.log(addPrefix("aaa"));
可変長引数
- 任意の数を受け取れる引数
- 書き方は
(...仮引数)
- スコープ内で
仮引数
と書き、入力の配列を受け取る
function fn(...arg) {
// argsに配列として入る
console.log(arg);
}
fn("a", "b", "c"); // ["a", "b", "c"]
複数の仮引数と使える
function fn(arg1, ...args) {
console.log(arg1); // "a"
console.log( args); // ["b", "c", "d"]
}
fn("a", "b", "c", "d");
Spread構文 配列の展開
-
関数(...配列)
というように渡す - 展開されて引数に渡される
// Spread構文 配列を展開して関数に渡す
function fn(x, y, z) {
console.log(x);
console.log(y);
console.log(z);
}
const array = [1, 2, 3];
fn(...array); // 1 2 3
分割代入(Destructuring assignment)
-
const {プロパティ名} = オブジェクト
で -
プロパティ名の変数
に同じプロパティ名のプロパティ値が入れられる - 一気に変数に入れられるので便利
分割代入ナシ
const user = {
"id":"123",
"userName": "Taro"
}
const id = user.id; // idに"123"
const userName = user.userName; // userNameに"Taro"
console.log(id);
console.log(userName);
分割代入アリ
const user = {
"id":"123",
"userName": "Taro"
}
const {id, userName} = user; // idに"123", userNameに"Taro"が入る
console.log(id);
console.log(userName);
関数での分割代入
function 関数名({ プロパティ名 }) {}
- オブジェクトのプロパティ名の値を仮引数として受け取る
// userオブジェクトのidプロパティの値を仮引数idとして受け取る
function printUserId({ id }) {
console.log(id)
}
printUserId(user); // "123"
関数の分割代入は**[配列]
**もできる
function print([first, second]) {
関数式
- 関数はオブジェクトなので、値としても扱える(第一級関数"ファーストクラスファンクション"という)
- 式なので変数に入れたりできる
-
関数式は
function
やArrow Function
で定義する
関数式の書き方① 変数名 = function() {};
const 変数名 = function() {};
const 変数名 = function 関数式名() {};
- 関数式は関数名を省略できる
- 名前を持たない関数を無名関数という
- 再帰的に関数を呼び出す場合などに使う(外部から呼ぶ必要がないから)
// 無名の再帰関数(0になるまで1引いて前の数値と掛け算)
const factional = function innerFact(n) {
if (n === 0) {
return 1;
}
return n * innerFact(n - 1);
};
console.log(factional(3)); // 6
関数式の書き方② 関数名 = () => {};
const 変数名 = (仮引数) => {return 返り値};
const 変数名 = () => {};
- Arrow Fancrionという書き方
- =>を使い無名関数を定義する
- 省略記法を使って短く記述できる
// Arrow Function
const fnA = () => {};
const fnB = x => {x * 2}; // 引数が1つなら()を省略可
const fnC = x => x * 2; // 一行なら{ブロック}も省略可
特徴
-
this
が静的に決定 -
new
できない -
arguments
を参照できない
コールバック関数
- 関数に引数として渡される関数
- コールバック関数を引数に使う関数・メソッドを高階関数と呼ぶ
- 非同期処理でよく利用する
// コールバック関数
const array2 = [1, 2, 3];
const output = (value) => {
console.log(value);
} ;
array2.forEach(output); //
// コールバック関数を直接引数として定義する
const array3 = [1, 2, 3];
array3.forEach(value => {
console.log(value);
});
メソッド
- オブジェクトがプロパティとして持つ関数のこと
- 定義 :
{メソッド名 : function(){処理}}
- 呼出 :
オブジェクト.メソッド名()
// メソッド
const objC = {
method1: function () {
// メソッド1の処理
},
method2: function () {
// メソッド2の処理
}
};
呼び出し
const objHello = {
funcHello: function () {
console.log("Hello World");
}
};
objHello.funcHello(); // メソッドfuncHelloを呼ぶ
メソッドの短縮記法(基本これで書く)
- ES2015より基本となった書き方
- オブジェクトリテラル
{}
の中に { メソッド名() {処理} };
// メソッドの短縮記法
const obj = {
method() { // メソッド名 ()
return "this is method";
},
};
console.log(obj.method());
式と文
JavaScriptプログラムは式と文からできている
式(Expression)
- 値を生成し変数に代入できるもの
- 式を評価すると評価値が得られる
- リテラルや関数式、
1 + 1
などの演算も式
console.log(1); // 1という数値リテラル(式)
console.log(1 + 1); // 1 + 1(式)の評価値は2
const fn = function() { // 関数式も式の一つ
return "hello";
};
文(Statement)
- 処理する一ステップのことでセミコロン
;
で区切る -
for文
やif文
など文のひとつ - 式は文になれる→式文
式文
- 文を書ける場所には式を書ける
- 文となった式を式文という
関数宣言と関数式の違い
- 関数宣言は文
- 関数式は式(
;
をつける)
function learn(){ // 関数宣言
}
const read = function() { // 関数式
};
条件分岐
- if文やfor文
- 条件によって処理を切り替える
if文
if (条件式) {処理}
-
(条件式)
がTrue
のとき処理される -
falsyな値は
False
と判断される
if ([]) {
console.log("配列はTrue");
}
if ({}) {
console.log("オブジェクトはtrue");
}
if ("もじ") {
console.log("文字列はtrue");
}
// falsyな例
if (!"") {
console.log("空文字はfalse");
}
if (!null) {
console.log("nullはfalse");
}
else、else if文
- 条件に一致しない部分を
else
で指定する - 複数条件の場合
else if
と書く
// else else if
const version = "ES6";
if (version === "ES5") {
console.log("ECMAScript 5");
} else if (version === "ES 6") {
console.log("ECMAScript 2015");
} else if (version === "ES7") {
console.log("ECMAScript 2016");
}
swich case 文
switch (条件式) {case1: 処理1; case2: 処理2;...}
- 厳密等価
===
で条件式とcaseを比較 - 一致したら処理する
- 必ずbreakをセットで使う(関数の処理内ならばreturnでも良い)
- switch ("jp") {
case "jp":
console.log("こんにちは");
break;
case "us":
console.log("Hello");
break;
case "cn":
console.log("你好");
break;
default:// どの条件にも合致しない時
break;
}
ループ 反復処理
while文
- 条件式の評価値が
true
なら反復処理をする - 条件式が
false
になったとき処理を抜ける while (条件式){ 処理 }
let x = 0;
console.log(`xの初期値: ${x}`);
while (x < 10) {
console.log(x);
x += 1;
}
console.log(`ループ処理終了 x : ${x}`);
do-while
- whileの処理が先に実行されるバージョン
do { 処理 } while(条件式);
let y = 20;
do {
console.log(y);
y -= 1;
} while (y > 10); // 20~11まで表示される
for文
- 繰り返し範囲を指定した反復処理をかく
for (初期化式; 条件式; 増分式) { 処理 }
// forでsum関数を実装する例
function sum(numbers) {
let total = 0;
for (let i = 0; i < numbers.length; i++) { // 配列の長さ分繰り返す
total += numbers[i];
}
return total;
}
console.log("合計は:" + sum([1, 2, 3, 4, 5])); // => 合計は15
forEach
: 配列の反復処理
- 配列の各要素それぞれに順に処理をするメソッド
- 書き方:
配列名.forEach( 引数 => {処理});
- 配列の先頭から
コールバック関数の引数
に要素が入る
// arrayのforEach
const num_array = [1, 2, 3];
num_array.forEach(num => { // numはコールバック関数の引数
console.log(num); // => 1, 2, 3
});
sum関数をforEachで書く例
// sum関数をforEachで実装する
function sum(numArray) {
let total = 0;
// numにnumArrayがひとつづつ入る
total = total + num;
numArray.forEach((num) => {});
return total;
}
console.log("合計は:" + sum([1, 2, 3, 4, 5]));
some関数
配列.some(引数 => {ture/fasleを返す目関数});
- または
配列.some(別でfunction定義したコールバック関数);
- tureが返った瞬間に反復を終了する
// some関数
function isEven(num) {
return num % 2 === 0;
}
const numbers = [1, 5, 10, 15, 20];
console.log(numbers.some(isEven)); // => true
filter(配列値をフィルターする)
配列.filter(コールバック関数);
- コールバック関数がTrueを返した要素のみを追加した新しい配列を返す
function isEven(num) {
return num % 2 === 0;
}
const numbers = [1, 5, 10, 15, 20];
// isEvenがTrueの値のみnubersから取り出す
console.log(numbers.filter(isEven)) // => [10, 20]
オブジェクトに対する反復処理
for in()
- オブジェクトのプロパティに対して反復処理をする
- ※意図しないプロパティまで列挙されてしまうので、なるべく使わない方がいい
for (プロパティのキー in オブジェクト) {プロパティに対する処理}
// for-inでオブジェクトのプロパティを列挙
const objA = {
"a": 1,
"b": 2,
"c": 3
}
for (key in objA) {
const value = objA[key];
console.log(`key:${key}, value:${value}`);
}
// key:a, value:1
// key:b, value:2
// key:c, value:3
for of文
- オブジェクトの値を取り出して反復処理
for(変数 of イテラブル) {処理}
- イテラブルとは、反復処理の動作が定義されたオブジェクトのこと(配列、文字列、Map、Setなど)
arrayX = [1, 2, 3];
for (value of arrayX) {
console.log(value)
}
// 1
// 2
// 3
オブジェクト詳細
- オブジェクトとはキーとバリューが対になったもの
-
{"キー(プロパティ)": "値"};
で定義 - プロパティは
""
を省略して書ける
const obj = {
"key": "balue"
};
// クオートの省略
const obj = {
key: "balue"
};
変数名として利用できない記号をキーに含める時は"obj-a"
の様にクオートで囲む
オブジェクトの値の省略
- プロパティ名と値に指定する変数名が同じ場合、値を省略できる
const fruit = "apple";
fluits = {
fruit
}
console.log(fluits); // {fruit: 'apple'}
Object
ビルドインオブジェクト
- =あらゆるオブジェクトの基になる実行環境に用意されたオブジェクト
-
new Object()
で空のオブジェクトを作成※{}(リテラル)
と同じ
const newObj = new Object();
console.log(typeof newObj); // => Object
オブジェクトを作成するとは、
Object
からインスタンスオブジェクト
を作成すること。
プロパティの参照の注意
- プロパティ名は暗黙的に文字列に変換される。
- ブラケット記法ではプロパティ名に変数を使って参照できる
const obj = {
key: "value",
123: 456,
"my-key": "my-value"
};
console.log(obj[123]); // => 456
console.log(obj["123"]); // => 456
const languages = {
ja: "日本語",
en: "英語"
};
cont myLang = "ja" // 変数をキーとして参照
console.log(langages[myLang]); // 日本語
変数へオブジェクトの値を一括で入れる(分割代入)
- 分割代入を使って変数に値を入れる
const {変数1, 変数2...} = Obj;
- プロパティ名と同じ変数に同じ値が入る
const languages = {
ja: "日本語",
en: "英語"
};
const { ja, en } = languages;
console.log(ja); // => "日本語"
console.log(en); // => "英語"
プロパティの追加
オブジェクト.新しいプロパティ=値
- 新しいキーとバリューを追加できる
- むやみにプロパティを追加するのは✖
- (なるべく最初の宣言の形を維持する)
const newObj = new Object();t
// newKeyと値を追加
newObj.newkey = "new value.";
console.log(newObj.newkey); //=> new value.
プロパティの削除
delete オブジェクト.プロパティ(キー)
delete newObj.newkey;
console.log(newObj); // => {} 空になる
Object.freeze()
オブジェクトの固定
- オブジェクトの変更を不可にする
- プロパティの追加削除,値の変更が出来なくなる
// オブジェクトの値を代入不可にする
const freezeObj = Object.freeze({key: "value"});
freezeObj.key = "new value";
console.log(freezeObj.key); //=> TypeError
プロパティの存在を確認
- 存在しないプロパティにアクセス=
undefined
が返りエラーは出ないから値がundefined
の場合混乱する - **
in
,Object.hasOwn()
**を使って確認する
in演算子
プロパティ名 in オブジェクト;
-
ture
かfalse
が返る
const obj = { key: undefined };
// `key`プロパティを持っているならtrue
if ("key" in obj) {
console.log("`key`プロパティは存在する");
}
Object.hasOwn()メソッド
Object.hasOwn(オブジェクト, "プロパティ");
- 対象のプロパティが存在すれば
ture
が返る(inと同様)
// objに"key"プロパティが含まれるか確認
Object.hasOwn(obj, "key"); // ture false
?.
Optional Chainingでプロパティの存在確認
- ネストしたプロパティは存在確認が大変
-
(widget.window.title)
みたいな
-
?.
を使ってプロパティの値を取る- 値が存在すれば
値
、なければその時点でundefined
を返す
// ifを使ったプロパティの存在確認
if (widget.window !== undefined && widget.window.title !== undefined) {
//widget.windowとwidget.window.titleを順にみないといけない
};
// ?.で存在確認
widget?.window?.title; // undefinedかtitleの値が返る
三項演算子を使ってプロパティが定義済みか確認する例
function printWidgetTitle(widget) {
// widget.window.titleが定義済み=>値
// widget.window.titleが未定義=>右辺の処理
const title = widget?.window?.title ?? "未定義";
console.log(`ウィジェットのタイトルは${title}です`);
}
オブジェクトの静的メソッド
-
Object
ビルドインオブジェクトそのものに実装されているメソッド - 静的メソッド(スタティックメソッド)
-
toString
hasOwn
など..
静的メソッドは、インスタンス元のオブジェクトから呼び出せるメソッドのことを言う。
例)
-
Object.keys
: キーの列挙 -
Object.values
: 値の列挙 -
Object.entries
: [キー: 値]の列挙
const testObj = {
one: 1,
two: 2,
three: 3,
};
console.log(Object.keys(testObj)); // ['one', 'two', 'three']
console.log(Object.values(testObj)); // ['one', 'two', 'three']
console.log(Object.entries(testObj)); // [["one", 1], ["two", 2], ["three", 3]]
オブジェクトのマージ
Object.assign(ターゲット, obj1, obj2...)
- オブジェクトの内容をコピーまたはマージする
const objectA = { a: "a" };
const objectB = { b: "b" };
// 空配列mergedにA, Bの合わせ新しく定義
const merged = Object.assign({}, objectA, objectB); // { a: "a", b: "b" }
// objAは変更されmarged===objAとなる
const merged = Object.assign(objectA, objectB);
Object.assign()
を使う場合は、
ターゲットに空オブジェクトを指定すると元の配列には影響がないので、一般的に上の様にする。
spread構文でのマージ
const objX = {a: "a"};
const objY = {b: "b"};
const mearged = {
...objX,
...objY
};
console.log(mearged); // {a: "a", b: "b"}
オブジェクトのコピー
- オブジェクトのコピーにも
Object.assign({}, コピー元)
として使う
const copyObj = Object.assign({}, obj); // コピーされる
shallow copy
assignメソッドでできるのは shallow copy(浅いコピー) で、
ネストされたオブジェクトは複製されない。
deep copy
プロパティ値まで再帰的にコピーする場合は再帰処理を用いて自分でdeep copyを実装する
- shallow copyする
- もしキーにオブジェクトがあれば再帰的にコピー
function deepClone(obj) {
const newObj = shallowClone(obj);
// プロパティがオブジェクト型であるなら、再帰的に複製する
Object.keys(newObj)
.filter(k => typeof newObj[k] === "object")
.forEach(k => newObj[k] = deepClone(newObj[k]));
return newObj;
}
配列
- JavaScriptの配列は可変長
- 値の追加や削除が出来る
-
push
,pop
-
- 配列のArray.メソッドには破壊的と非破壊的メソッドがある。
Array.length
- 配列の要素数を返す
// 配列のメソッド
const arrayA = [1, 2, 3];
console.log(arrayA.length); // => 3
疎な配列と密な配列
- 配列に空の要素がある配列→疎な配列
- 疎を参照すると
undefined
が返る
// 疎な配列
const arrayB = [4, , 6];
console.log(arrayB[1]) // => undefined
Array.at(index)
:要素の参照
- 配列の要素を参照する
-
Array.at(-1)
など負の数で後ろから参照 - (Array[Array.length -1]より楽)
const array = ["a", "b", "c"];
//
console.log(array.at(0)); // => "a"
console.log(array.at(1)); // => "b"
// 後ろから1つ目の要素にアクセス
console.log(array.at(-1));
※配列[-1]と書くと、大抵の場合はundefinedが返ってしまう(無いプロパティへのアクセス)
Array.isArray(obj)
:配列かオブジェクトかの判断
-
true
false
が返る - ※
typeof
演算子では配列も[Object]と判定されるので判定できない
const obj = {};
const array = [];
console.log(Array.isArray(obj)); // => false
console.log(Array.isArray(array)); // => true
分割代入
- 配列もオブジェクト同様、分割代入が出来る
const arrayN = ["first", "second", "third"];
const [first, second, third] = arrayN;
console.log(first); // first
console.log(second); // second
console.log(third); // third
hasOwn
で疎かUndefinedか判定する
- 配列は参照外、空、undefinedを参照するとどれもundefinedが返る
- 指定したIndexが空なのかundefinedかを見分けるには
Object.hasOwn(配列, index)
const arrayB = [4, , 6];
console.log(Object.hasOwn(arrayB, 1)); // false
falseなら要素自体が存在しない
indexOf
lastIndexof
:要素でインデックスを取得
-
Array.indexOf(要素)
でインデックスの位置が返る - 一致するものが無いとき
-1
が返る
const animals = ["サル", "鳥", "犬"];
const birdIndex = animals.indexOf("鳥");
console.log(birdIndex); // => 1
const fishIndex = animals.indexOf("魚")
console.log(fishIndex); // => -1
array.find
: 条件に一致する要素を取得
配列名.find(コールバック関数)
- コールバック関数には条件を入れる
- 一致する要素そのものが返る
const colors = [{ color: "red" }, { color: "green" }, { color: "blue" }];
// find
const greenColor = colors.find((obj) => {
return obj.color === "green";
});
console.log(greenColor); // {color: 'green'}
// 10より大きい`count`プロパティを持つ最初のオブジェクトを取得
const firstRecord = records.find((record) => {
return record.count > 10;
});
console.log(firstRecord); // { date: "2020/12/2", count: 11 }
array.findIndex
: 条件に一致するIndexを取得
配列名.findIndex(コールバック関数)
- コールバック関数には条件を入れる
// findIndex
const blueColor = colors.findIndex((obj) => {
return obj.color === "blue";
});
console.log(blueColor); // 2
Array.slise
:範囲を指定して取得
配列名.slise(始点, 終点);
- 新しい配列として範囲を取り出す
// slise
const numArray = [1, 2, 3, 4, 5];
console.log(numArray.slice(1, 4)); // [2, 3, 4]
Array.includes()
:要素が含まれているか
- 配列に要素が含まれている場合、
true
を返す
const array = ["Java", "JavaScript", "Ruby"];
// `includes`は含まれているなら`true`を返す
if (array.includes("JavaScript")) {
console.log("配列にJavaScriptが含まれている");
}
配列への追加・削除
array.push()
末尾に追加
配列名.push(追加要素)
- 先頭への追加は
配列名.unshift(追加要素)
array.pop()
:末尾を削除
配列名.pop()
- 先頭の削除は
配列名.shift()
const array = ["A", "B", "C"];
array.push("D"); // "D"を末尾に追加
console.log(array); // => ["A", "B", "C", "D"]
const poppedItem = array.pop(); // 最末尾の要素を削除し、その要素を返す
console.log(poppedItem); // => "D"
console.log(array); // => ["A", "B", "C"]
const array = ["A", "B", "C"];
array.unshift("S"); // "S"を先頭に追加
console.log(array); // => ["S", "A", "B", "C"]
const shiftedItem = array.shift(); // 先頭の要素を削除
console.log(shiftedItem); // => "S"
console.log(array); // => ["A", "B", "C"]
array.splice()
: 任意のインデックスの追加・削除
array.splice(インデックス, 削除する要素数);
array.splice(インデックス, 削除する要素数, ...追加する要素);
const array = ["a", "b", "c"];
// 1番目から1つの要素("b")を削除
array.splice(1, 1);
console.log(array); // => ["a", "c"]
console.log(array.length); // => 2
console.log(array[1]); // => "c"
// すべて削除
array.splice(0, array.length);
console.log(array.length); // => 0
array.concat()
:配列の結合
結合元配列.concat(結合したい配列)
- 合体した新しい配列が作成される
const array = ["A", "B", "C"];
const newArray = array.concat(["D", "E"]);
console.log(newArray); // => ["A", "B", "C", "D", "E"]
...
: 配列の展開
- spread構文で配列を任意の位置で展開
- concatと異なりどこでも結合できる
const charArray = ["a", "b", "c"];
const charsArray = ["x", ...charArray, "x"];
console.log(charsArray); // ['x', 'a', 'b', 'c', 'x']
array.flat
:配列のフラット化
- 多重の配列
[[[]]
を[]
にする - flat化する深さは
array.flat(深さ)
で指定 - 全階層フラットにする場合、引数に
Infinity
を指定する
const multiArray = [[[1], 2], 3];
console.log(multiArray.flat(2)); // [1, 2, 3]
array.length
を利用した要素削除
- 配列要素を一括削除する時、
length
プロパティに0
を代入する array.length = 0 // []空の配列に
破壊的・非破壊的メソッド
- 配列操作には2つある
- Mutable(破壊的)なメソッド
- immutable(非破壊的)なメソッド
- 出来るだけ非破壊の利用が望ましい
- 破壊的:対象の配列自体を操作
- 非破壊的:対象のコピーを操作する
https://jsprimer.net/basic/array/
例)pop
push
splise
→破壊的
.at(-1)
...
toSpliced
→非破壊的
配列の反復処理
forEach
- 配列の要素を取出し先頭から処理する
- 返値を返さない
- (元の配列は操作しない)
const array = [1, 2, 3];
const doubleArray = array.forEach((value, index, array) => {
return value * 2;
});
console.log(doubleArray); // undefined
map
- 配列の要素を先頭から処理する
- 処理した結果を適応した新しい配列を返す
const array = [1, 2, 3];
const doubleArray = array.map((value, index, array) => {
return value * 2;
});
console.log(doubleArray); // [2, 4, 6]
filter
- 配列から不要な要素を取り除く
- コールバック関数は
true
falase
を返し -
true
の要素だけ集めた新しい配列を返す
const filterArray = array.filter((num) => {
return num % 2 == 0;
})
console.log(filterArray); // [2]
reduce
- 配列要素の合計値を返す
reduce(一つ目の値, 2つ目の値) => {return 一つ目の値 + 2つ目の値}
- 配列の前から順に残りを足していくイメージ
const sumValue = array.reduce((acc, curentValue) => {
console.log(`acc:${acc}, curent:${curentValue}`);
// acc:1, curent:2
// acc:3, curent:3
return acc + curentValue;
});
console.log(sumValue); // 6
groupBy
- 配列の要素を条件でグループ分けし新たな配列を作成する
- (フィルターとは違い条件に合致しないものもグループ化する)
const array = [1, 2, 3, 4, 5];
const grouped = Object.groupBy(array, (currentValue) => {
// currentValueが偶数なら"even"、そうでないなら"odd"の配列に追加される
return currentValue % 2 === 0 ? "even" : "odd";
});
console.log(grouped.even); // => [2, 4]
console.log(grouped.odd); // => [1, 3, 5]
メソッドチェーン
[対象].mesod1().mesod2()...
- メソッドをつなげて処理を連続させること→メソッドチェーンという
- 簡潔に処理を書ける
const array = ["a"].concat("b").concat("c");
console.log(array); // => ["a", "b", "c"]
例)
-
filter
で年を絞り込み -
map
で結果をまとめて返す
// ECMAScriptのバージョン名と発行年
const ECMAScriptVersions = [
{ name: "ECMAScript 1", year: 1997 },
{ name: "ECMAScript 2", year: 1998 },
{ name: "ECMAScript 3", year: 1999 },
{ name: "ECMAScript 5", year: 2009 },
{ name: "ECMAScript 5.1", year: 2011 },
{ name: "ECMAScript 2015", year: 2015 },
{ name: "ECMAScript 2016", year: 2016 },
{ name: "ECMAScript 2017", year: 2017 },
];
// メソッドチェーンで加工処理を並べる
const versionNames = ECMAScriptVersions
// 2000年以下のデータに絞り込み
.filter(ECMAScript => ECMAScript.year <= 2000)
// 各要素から`name`プロパティを取り出す
.map(ECMAScript => ECMAScript.name);
console.log(versionNames);
// => ["ECMAScript 1", "ECMAScript 2", "ECMAScript 3"]
文字列
文字へのアクセス
- 配列風のアクセスと
.at()
がある 文字列[index]
文字列.at(±num)
// 文字列
// 配列でアクセス
const str = "ABCD";
console.log(str[1]); // B
console.log(str[2]); // C
// String.prototype.atでアクセス
console.log(str.at(0)) // A
console.log(str.at(1)) // B
console.log(str.at(-1)) // D
at
はStoringプロトタイプオブジェクトのメソッド。
carAt
というメソッドもある(at
との違い:負のとき空文字を返す)
文字列の結合・分解
-
.split("")
:文字列の分解 -
.join("")
:文字列の結合
// split 文字列の分解
const strings = "赤・青・黄";
let spStr = strings.split("・");
console.log(spStr); // ["赤", "青", "黄"]
// join 文字列の結合
spStr = spStr.join("&");
console.log(spStr); // 赤&青&黄
同時に使うことがよくある
const tea = "玄米茶";
spTea = tea.split("").join("★");
console.log(spTea); // 玄★米★茶
正規表現でのsplit()
正規表現でマッチを探してsplit
できる
// 1つ以上のスペースの正規表現➢ /\s+/
const space = "a ba c";
spSpace = space.split(/\s+/);
console.log(spSpace); // ['a', 'ba', 'c']
文字列の大小
JavaScriptは文字コードとしてUnicode、エンコードする方式としてUTF-16を採用している
- 文字コード:文字を01のビットに変換したもの(Unicode)
- エンコード:文字コードをさらにコンピュータ用に変換すること(UTF-8, UTF-16)
// CよりDが、文字コードは後ろ
console.log("ABC" > "ABD"); // => false
文字列はコードユニットの並びで、
先頭から比較される。( コードユニットは例だと3つ)
文字の切り出し
文字列から任意の部分を切り出す
slice()
substring()
slice(始点,終点)
const alphabet = "abcdefg";
console.log(alphabet.slice(0, 3)); // abc
console.log(alphabet.slice(2, 5)); // cde
console.log(alphabet.slice(-2)); // fg
負の数は後ろから数える
substring(始点, 終点) ※負の数は"0"扱い
console.log(alphabet.substring(0)) // abcdefg
console.log(alphabet.substring(3, 5)) // de
console.log(alphabet.substring(-1)) // abcdefg
いずれも非破壊的なメソッドで機能の違いもないので好みで使う。
文字列の検索
IndexOf()
lastIndexOf()
IndexOf("検索したい文字列")
- 前方から検索して一致したインデックスを返す
lastIndexOf("検索したい文字列")
- 後方から検索して一致したインデックスを返す
const fruits = "ばななリンゴぶどうリンゴオレンジ";
console.log(fruits.indexOf("リンゴ")); // 3
console.log(fruits.indexOf("ぶどう")); // 6
console.log(fruits.lastIndexOf("リンゴ")); // 9 後ろ側のリンゴの先頭index
いずれのメソッドも一致しない場合-1
を返す
console.log(fruits.indexOf("ブドウ")); // -1
console.log(fruits.lastIndexOf("ブドウ")); // -1
- 応用例
// "いちご"が無い場合、indexOfは-1を返す
if (fruits.indexOf("いちご") != -1) {
console.log("いちごがあります!");
} else {
console.log("いちごはありません");
}
文字列の置換
特定の文字列を別の文字(列)に置き換える
replace()
replarceAll()
replace( "検索対象", "置きかえ文字")
replace( /正規表現/, "置きかえ文字")
// 直接指定
const phrase = "にわにはにわにわとりがいる";
console.log(phrase.replace("にわとり", "ハト"));
// => にわにはにわハトがいる
// 正規表現
console.log(phrase.replace(/にわ/g, "ハト"));
// => ハトにはハトハトとりがいる
replaceAll("検索対象", "置きかえ文字")
- replace()との違い
- replace: 最初の一致しか変換しない
- replaceAll: すべての一致を置きかえる
const text = "0120-117-117";
const result = text.replaceAll("-", "($&)");
console.log(result);
// => 0120(-)117(-)117
コールバック関数を用いた複雑な変換
- replace, replaceAllは第2引数にコールバック関数を渡せる
repllace("対象", ()=> { 処理 });
repllaceAll("対象", ()=> { 処理 });
複雑に年月日などの置き換えが可能
function toDateJa(dateString) {
// マッチした対象のみコールバック関数で置換処理
return dateString.replace(
/(\d{4})-(\d{2})-(\d{2})/g,
(all, year, month, day) => {
// allはマッチした文字列全体が入っている(未利用)
return `${year}年${month}月${day}日`;
}
);
}
console.log(toDateJa("2024-01-02")); // 2024年01月02日
code point と code unit
- JavaScriptはUTF-16でエンコードしたcode unit単位で文字を認識する
- code point = unicodeに対応した1文字1IDの単位(実質的な1文字)
// code point
const aiu = "あイウ";
console.log(aiu.codePointAt(0)); // 12354
console.log(aiu.codePointAt(1)); // 12452
// 16進数表示
console.log(aiu.codePointAt(0).toString(16)); // 3042
console.log(aiu.codePointAt(1).toString(16)); // 30a4
※JavaScriptは
code unitで文字を取扱うメソッドとcode pointで取扱うメソッドがある。
code unitの注意点
- code unit = 1文字ではない!
- 絵文字など複数code unitで表される文字がある
- ➢サロゲートペア という
const ringo = "🍎"
console.log(ringo.length); // 2
※例として🍎はlength(code unit)が2つ
code pointで数える・扱うには?
- array.from()で配列に変換する
- 正規表現で
\u{unicode}
フラグを使う
const ringo = "これは🍎です"
console.log(ringo.length); // 7
const codePoints = Array.from(ringo);
console.log(codePoints); // ['こ', 'れ', 'は', '🍎', 'で', 'す']
console.log(codePoints.length); // 2
文字数(code point数)がlengthで出力
console.log("\u{1F34E}"); // => "🍎"
正規表現はデフォルトでcode unit単位で処理される。
code pointでmatchなどさせるなら\u
でUnicodeであることを示す。
ラッパーオブジェクト
- プリミティブ値のプロパティやメソッドにアクセスすると自動で生成されるオブジェクト
以下は明示的に生成しているが、
自動で作成され参照後破棄されている。
// testStrはプリミティブだが、メソッドが呼べる
const testStr = new String("input Value");
console.log(testStr.toUpperCase()); // INPUT VALUE
// 自動でラッパーオブジェクトに変換される
const testStr = "input Value";
console.log(testStr.toUpperCase()); // INPUT VALUE
プロパティとメソッドって違うん?
プロパティ≒データとメソッドのこと
データの方をデータプロパティという(.length
とか)
動作 | 例 | 説明 |
---|---|---|
プロパティアクセス | str.length |
ラッパーオブジェクトのプロパティにアクセスする。 |
メソッドの呼び出し | str.toUpperCase() |
ラッパーオブジェクトのプロパティ(関数)を実行する。 |
ラッパーオブジェクトを持つ型
-
null
とundefined
以外のプリミティブ
ラッパーオブジェクト | プリミティブ型 | 例 |
---|---|---|
Boolean | 真偽値 |
true やfalse
|
Number | 数値 |
1 や2
|
BigInt | BigInt |
1n や2n
|
String | 文字列 | "文字列" |
Symbol | シンボル | Symbol("説明") |
スコープ
- スコープとは変数や関数がアクセス可能な範囲
- スコープ内の変数や関数は他スコープからアクセスできない
関数スコープ
- 関数内の仮引数やローカル変数はその関数内でのみ使える
function testFunc(x) {
console.log(x);
}
testFunc("hello"); // "hello"
console.log(x); // x is not defined
ブロックスコープ
- ブロック
{}
で囲まれたスコープ -
if
やfor
などで使われているのと同じ
{ // ブロックスコープ
let blockStr = "abc";
}
console.log(blockStr); // blockStr is not defined
※for of
文などはループ毎にスコープを作成する
スコープチェーン
- ネストされたスコープで変数や関数を参照する際
- 内側=>外側に向かってたどる仕組みのこと
- 内側をINNER, 外側をOUTERと呼ぶ場合アリ
{
// OUTERブロックスコープ
{
// INNERブロックスコープ
}
}
内側のスコープから外側の変数や関数は
参照できる
外側から内側は参照できない
グローバルスコープ
- どのブロックからも参照できる一番外のスコープ
- ここに変数を定義するとグローバル変数となる
const globalVariable = "グローバル変数"
// ビルドインオブジェクトもグローバル
Array;
isNaN;
!変数の隠蔽に注意
- 内側のスコープで外側の変数名を定義すること
- => 意味が上書きされるので先の定義を参照できなくなる
関数などを活用して小さなスコープでプログラムを書く事が重要。
var
function
の変数巻上げ
-
var
やfunction
は - 定義の前に参照できる巻上げが起こる
- ※Hoisting(ホイスト)と呼ばれる
var
の巻き上げ
- varは宣言より手前で参照できてしまう
- スコープも無視され、最も近い関数スコープの先頭に宣言されたような挙動がおこる
// var num; ←ここに巻き上げ
console.log(varNum); // undefined
{
var varNum = 123;
console.log(varNum); // 123
}
避け方
var
を使わずlet
を利用する
console.log(varNum); // varNum is not defined
// reference Errorとなり分かる
{
let varNum = 123;
console.log(varNum); // 123
}
関数function()
の巻き上げ
- 関数宣言より先に呼び出せる挙動
- 関数そのものが先頭に移動したような形になる
- ※
var
の巻き上げに比べ悪影響は少ない
hello(); // => "Hello"
function hello(){
return "Hello";
}
※ただしvar hello = ()=> {}
のような関数式はvar
の巻き上げを食らうので注意
即時実行関数(IIFE)
- グローバルスコープを汚さないための関数の書き方
- 初期化処理などにも使う
-
var
でも書けるが、昨今は{}
やconst, let
を使って書く
(function() {
// 関数のスコープ内でfoo変数を宣言している
var foo = "foo";
console.log(foo); // => "foo"
})();
即時実行関数(IIFE)の書き方
-
(無名関数{})();
で即時実行される
(function() {
// 関数の内容
})();
constを使った例
(function() {
const foo = "foo";
console.log(foo); // => "foo"
})();
この場合、グローバルスコープを汚さない目的なら以下の様にブロックを使えばよい
{
// 上の即時実行関数と同じ処理
const foo = "foo";
console.log(foo); // => "foo"
}
クロージャ
- 関数が外スコープの変数を参照している限り、その変数の値を覚えている仕組み
- => 関数内の変数に状態を作り出せる
クロージャの書き方
- 関数を定義
- 関数内に
状態保持の変数
を作成 -
return
状態保持の変数
を操作する関数
function createCounter() {
// クロージャの保持対象は'count'変数
let count = 0;
// createCounterはincrement関数を返す
return function increment () {
count = count + 1;
console.log(count);
};
}
// 呼び出しは新たな変数に代入し()で処理を呼ぶ
const counter1 = createCounter();
console.log(counter1) // ƒ increment ()
counter1(); // 1
counter1(); // 2
プロパティVSクロージャ
状態はオブジェクトとプロパティでいいのでは?
➢プロパティは外部から参照・操作できる
変数の状態を隠蔽したい時は
クロージャを利用する
- クロージャの例
function createCounter() {
let count = 0; // この変数は外部からアクセスできない
return function() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
- オブジェクト&プロパティの例
const counter = {
count: 0, // countプロパティを作成
increment: function() {
this.count++;
console.log(this.count);
}
};
counter.increment(); // 1
// countは外部から直接アクセスできる
console.log(counter.count); // 3
this キーワード
-
this
は呼出し方によって意味が変わるキーワード - ある時はオブジェクト、又ある時はプロパティ、undefinedなど...
- コードの簡略化やオブジェクトの関係を簡潔に表すために使う
strict modeのthis
- 通常、トップレベルに書かれた
this
は```グローバルオブジェクトを指す- ブラウザ:
Window
- node.js:
Global
- ブラウザ:
// ブラウザ
console.log(this); // => Window
// node
console.log(this); // => Global
実行コンテキスト(※環境)によって示すオブジェクトが変わるのでバグの原因になる
strict modeならthis
= undefined
- strict modeでは
this
はundefined
が返される -
モジュールはstrict modeが常に有効。よって
this
はundefined
になる
<script type="module">
// 実行コンテキストは"Module"
console.log(this); // => undefined
</script>
関数とメソッドにおけるthis
- 関数とメソッドにおいてはArrow Functionと通常の関数で挙動が違う
-
通常の関数
:呼び出し時に決定 -
ArrowFanction
:定義した時に決定
メソッド呼出し(通常の関数)
- メソッドは何らかのオブジェクトに属する
-
this
は呼出し時に決まる
const person = {
fullName: "Brendan Eich",
sayName: function() {
return this.fullName; // `person.fullName`と書いているのと同じ
}
};
console.log(person.sayName()); // "Brendan Eich"
上記例では、this
は
ベースオブジェクト(person
)を示す文言になる
※ベースオブジェクトとは
オブジェクト.メソッド()
オブジェクト.データプロパティ
- として呼ぶ際の左の
オブジェクト.
の部分
呼出し時にthis
が決まる弊害
通常関数のthis
は呼出し時に参照元が決まるから、問題が起こる
- メソッドを外部の変数に代入して実行したとき
-
オブジェクト
のスコープ外なのでthis
が行方不明になる(global
を指しちゃう)
-
const person = {
fullName: "Brendan Eich",
sayName: function() {
return this.fullName; // `person.fullName`と書いているのと同じ
}
};
// `person.fullName`を出力する
console.log(person.sayName());
const say = person.sayName;
// personのsayNameだけ取出しsayに代入
// `this`はundefinedとなってしまう
console.log(say());
対処法
- Functionオブジェクトの専用メソッドで
this
を指定する関数.call(thisの対象, 引数...);
関数.apply(thisの対象, [引数]);
関数.bind(thisの対象, 引数);
※bindは関数にthisを拘束した新たな関数を作成
"use strict";
function say(message) {
return `${message} ${this.fullName}!`;
}
const person = {
fullName: "Brendan Eich"
};
// いずれもthisを明示的に指定するメソッド
console.log(say.call(person, "こんにちは"));
console.log(say.apply(person, ["こんにちは"]));
const sayPerson = say.bind(person, "こんにちは");
// "こんにちは Brendan Eich!"
その他、callback関数でthisを使う場合も問題がある
Arrow Functionのthis
- Arrow Functionでは1つ外側のthisを引き継ぐ
- 定義時にこのthisは固定され文脈により変更されない
const obj = {
method() {
const arrowFunction = () => {
// methodにおけるthis(=obj)を引き継ぐ
return this;
};
// methodはarrowFunction()を返す
return arrowFunction();
},
};
console.log(obj.method()); // obj
静的なthisの決定を確認
const arrow = obj.method();
// functionと違いthisが静的に決定する為、Globalではなくobjが返る
console.log(arrow); // obj
クラス
- クラス:動作や状態を定義した構造のこと
- インスタンス:クラスから生成したオブジェクト
- インスタンスはクラスに定義した構造を継承する
クラスの定義方法(2つ)
- クラス宣言
class ClassName{}
- クラス式
variable = class Classname{}
// クラス宣言文
class MyClass {
constructor() {
}
}
// クラス式
const AnonymousClass = MyClass {
constructor()
}
クラス定義では
慣習的に大文字で始まるパスカルケースを使う。例) MyNewClass
クラス式ではクラス名を省略できる
無名クラスという書き方
const MyClass = class {
constructor() {
}
}
constructor
関数
コンストラクタとはクラス=>インスタンスを作成する時に、
インスタンスの状態の初期化をするメソッドのこと。
constructor(){コンストラクタの処理}
- (クラスをインスタンス化したとき自動的に呼び出される)
class MyClass {
constructor() {
// 初期化に関する処理
}
}
class MyClass {
// コンストラクタの省略
}
※コンストラクタは処理が無ければ省略可能である
クラスのインスタンス化new
- クラスからインスタンス作成することをインスタンス化という
-
new
class名();
→でインスタンス化
インスタンスがどのクラス空作成されたかチェックする際はinstansof
:が便利。
// インスタンス化
const myClass = new MyClass();
// 即時でもインスタンス化可能
console.log(new MyClass()); // MyClass {}[[Prototype]]: Object
console.log(myClass instanceof MyClass); // true
処理のあるコンストラクタの例
以下の例でコンストラクタは
- インスタンス化時、仮引数
x
y
を受け取る -
this
(生成したコンストラクタ)にプロパティx
y
を設定する -
x
y
に仮引数値を代入
class Point {
constructor(x, y) {
// this.x, this.yは
// インスタンスのプロパティになる
this.x = x;
this.y = y;
}
}
console.log(new Point(1, 2)); // {x: 1, y: 2}
this
はコンストラクタにおいて生成したコンストラクタを示す
注意:コンストラクタ関数でオブジェクトを返すのは✖
コンストラクタでreturn
した値はnew
した際の返り値になる
➢本来new
は生成したインスタンスを返すべきなので、好ましくない。
class Square {
constructor(x, y) {
this.x = x;
this.y = y;
return { x, y }; // 別のオブジェクトを返す
}
}
console.log(new Square(100, 200));
// Squareクラス"ではない"クラス{x: 100, y: 200}
プロトタイプメソッド
- JavaScriptのクラスが持つメソッド
- あるクラスから作成されたコンストラクタ間で参照が共有される
- (クラスのメソッドは全てこれ)
メソッドの定義
メソッド名(){メソッドの処理}
class Counter {
constructor() {
// thisはCounterのインスタンスを示す
this.count = 0;
}
// incrementメソッドを定義
increment() {
this.count++;
}
}
メソッドの呼出し
コンストラクタ.メソッド(引数)
const counterA = new Counter();
const counterB = new Counter();
// メソッド呼出し
counterB.increment(); // countプロパティ: 0 -> 1
プロパティの参照:コンストラクタ.プロパティ
// プロパティ確認
console.log(counterB.count) // 1
メソッドの共有
CounterAとCounterBでは、
incrementメソッドは共有されている
// 以下はtureになる
console.log(CounterA.increment === counterB.increment);
この様にプロトタイプオブジェクトの仕組みでは、
同クラスの別インスタンス同士でメソッドの参照を共有する
アクセッサプロパティgetter
setter
- クラスのプロパティを参照・代入する際に使うメソッド
- 参照は
getter
を使う - 代入は
setter
を使う
- 参照は
(メソッドだが使い方はプロパティチックな為、アクセッサプロパティと呼ぶ)
なぜアクセッサプロパティが必要なのか?
秘匿性や安全性、利便性にメリットがあるから。
目的 | 説明 |
---|---|
データのカプセル化 | 内部データを隠しつつ安全にアクセス・更新する。 |
動的な処理の追加 | 値の取得・設定時にバリデーションや計算を行える。 |
データの整合性確保 | 不正な値の設定を防ぎ、一貫性を保つ。 |
柔軟なAPI設計 | 外部からの使い方を変えずに内部実装を調整可能。 |
書き方
- コンストラクタ定義後
-
get プロパティ名(){return 返値}
- ※ returnはgetterにおいて必須
-
set プロパティ名(仮引数){set処理}
- ※ 値を返す必要は無い
class NumWrapper {
constructor(value) {
return (this._value = value);
}
// getter
get value() {
return this._value;
}
// setter
set value(newValue) {
this._value = newValue;
}
}
get
set
呼び出し
- get
インスタンス名.プロパティ名
- set
インスタンス名.プロパティ名 〇〇処理
// インスタンス化
const numWrapper = new NumWrapper(10);
// getで表示
console.log(numWrapper.value); // 10
// setで代入
numWrapper.value = 30;
console.log(numWrapper.value); // 30
注意:_
から始まるプロパティ
プロパティには直接参照されたくない場合があり、_value
の様にする慣習があった
(現在は**#value**
のようなPrivate class fieldという機能が用意されている)
クラスフィールド
- インスタンスの持つプロパティをより簡単に指定するための構文
- 外部からアクセス出來るクラスフィールドを、publicクラスフィールドという
プロパティをプロパティ = 初期値
と書ける
class MyTestClass {
// クラスフィールドによるプロパティ
property1 = "Class Field";
// コンストラクタによるプロパティ
constructor(arg) {
this.property2 = arg;
}
}
const myNewClass = new MyTestClass(2);
console.log(myNewClass.property1) // "Class Field"
console.log(myNewClass.property2) // 2
初期値を省略したプロパティ(クラスフィールド利用。省略で初期値はundefined
となる)
class Loader {
loadedContent;
load() {
this.loadedContent = "読み込んだコンテンツ内容";
}
}
クラスフィールドのthis
クラスフィールドのthis
はクラスのインスタンスを示す
class Counter {
count = 0;
// thisはCounterインスタンスを示し、
// upはそのincrementメソッドを参照している
up = this.increment;
increment() {
this.count++;
}
}
この性質どう使う?
➢ArrowFunctionと組み合わせて、
関数としてメソッドを実行する際におこる、thisの呼出し時決定の性質によるエラーに対処できる
class Counter {
count = 0;
increment() {
// thisはCounterインスタンスを指す
this.count++;
}
}
const newCounter = new Counter();
// incrementを変数に代入し、次で呼出す
// thisがスコープを外れundefinedになる
// エラーが発生
const increment = newCounter.increment;
increment(); // Uncaught TypeError
上記はメソッドを関数として呼ぶ際、
this
がglobalオブジェクトを指してしまう**ことが原因。
(strict modeではundefined
)
対処
クラスフィールド上でメソッド呼出しをArrowFunctionで定義すると、thisが固定される
class Counter {
count = 0;
// countUpフィールドに関数式
// thisがインスタンスに固定される
countUp = ()=> {
this.increment();
}
increment() {
this.count++;
}
}
const newCounter = new Counter();
const up = newCounter.up;
up; // 正しくincrement
Privateクラスフィールド
- クラスフィールドを
#field
と記述 - インスタンス化後にクラス内からのアクセスに限定できる※プロパティ参照などできない
-
getter
setter
と併せて使える
class TestClass {
_value; // フィールド定義
}
const testClass = new TestClass();
console.log(testClass._value); // undefined
上記例では_value
フィールドにアクセスできる
private化
#value
で参照した際構文エラーでアクセス不可にできる
class TestClass {
#value; // フィールド定義
constructor() {
this.#value = undefined;
}
}
const testClass = new TestClass();
console.log(testClass.#value); // error
静的メソッド
- クラスをインスタンス化せず利用できるメソッド
- クラスメソッドとも呼ぶ
- メソッドの前に
static
をつける
数値を受けとりインスタンス化する静的メソッドof
を実装した例
class ArrayWrapper {
constructor(array = []) {
this.array = array;
}
// ...valueで入力を配列に変換して受け取る(残余引数)
static of(...items) {
return new ArrayWrapper(items); // ArrayWrapperをインスタンス化
}
get length() {
return this.array.length;
}
}
const ArrayWrapperA = new ArrayWrapper([1, 2, 3]);
// new でインスタンス化せずofメソッドを利用する
// of内でインスタンスを生成
const ArrayWrapperB = ArrayWrapper.of(1, 2, 3);
console.log(ArrayWrapperB.length)
また上記of
のnew ArrayWrapper
は、thisでも良い
static of(...items){
return new this(items);
}
静的クラスフィールド
- クラス自体(Prototypeオブジェクト)に設定されたフィールド(プロパティや変数)
static フィールド名
// 静的クラスフィールド
class Colors {
static red = "赤";
}
// インスタンス化せずクラスフィールドを呼び出す
console.log(Colors.red) // "赤"
また、#(private)
をつけて外部から秘匿もできる
// 静的クラスフィールド
class Colors {
static #red = "赤";
}
// 非公開のプロパティにアクセスしているので
// 未定義undefinedが返る
console.log(Colors.red) // undefined
プロトタイプに定義したメソッド、インスタンスに定義したメソッド
- プロトタイプに定義したメソッド
- クラスにメソッド構文
method(){}
で定義 - プロトタイプオブジェクトとして保存
- インスタンス間で共有
- クラスにメソッド構文
- インスタンスに定義したメソッド
- クラスから作成したインスタンス毎に定義されるメソッド
- アロー関数
method = ()=> {}
で定義
プロトタイプチェーン
- インスタンスにプロトタイへの参照を記録する
- インスタンスからプロパティを探す際、インスタンス➢プロトタイプオブジェクトまで探索する機能
class MyClass {
method() {
console.log("プロトタイプのメソッド");
}
}
const instance = new MyClass();
// インスタンスには`method`プロパティがないため、プロトタイプオブジェクトの`method`が参照される
instance.method(); // "プロトタイプのメソッド"
// `instance.method`の参照はプロトタイプオブジェクトの`method`と一致する
const Prototype = Object.getPrototypeOf(instance);
console.log(instance.method === Prototype.method); // => true
-
class1.prototype
とclass2.prototype
は異なるオブジェクトで、個別にメソッドやプロパティを持つ。 - 両者は共通で
Object.prototype
を参照し、Object.prototype
のメソッドは利用可能。 - クラス間でメソッドやプロパティを共有するには、継承を使う必要がある。
- 継承により、サブクラスの
prototype
は親クラスのprototype
を参照する。
継承
- クラスの構造や機能を引き継いだクラスを作ること
-
extends
キーワードを使う
。
extends Parent
で親クラス(継承元)のメソッドを継承し呼び出す例
class Parent {
name; // プロパティ定義
hello = (name) => {
this.name = name; //プロパティに代入
console.log(`hello, ${this.name}`);
};
}
class Child extends Parent {
// Parentのメソッドhelloを継承
}
const childClass = new Child;
// 変数として呼出し
const x = childClass.hello;
x("wawawa"); // hello, wawawa
super()
親クラスのコンストラクタを継承
- 単純な継承のみなら、自動でコンストラクタ・メソッドは勝手に継承される
-
super()
を使う場合↓- 子クラスで独自のコンストラクタを定義する
- メソッドをオーバーライドする
class Parent {
constructor(...args) {
console.log("constructor1:" + args);
}
}
// ---Parentコンストラクタを引き継いだ上
// ---独自に処理を追加する
class Child extends Parent {
constructor(...args) {
// 親クラスにもargsを引き継ぐ
super(...args);
console.log("constructor2:" + args);
}
}
const child = new Child(1,2);
// constructor1:1,2
// constructor2:1,2
super()
をつけないで新たなコンストラクタを定義するとエラーになる
class Child extends Parent {
constructor(...args) {
console.log("child constructor:" + args);
}
}
クラスフィールド(プロパティ)の継承
- クラスのフィールドもインスタンスに継承される
- この場合インスタンスのフィールドとして定義される
※親クラスでprivate
なフィールドは継承先でもprivate
となる
class Parent {
field = "field.";
#privateField = "private field.";
}
class Child extends Parent {
// フィールドは両方継承
showPrivate(){
console.log(this.#privateField);
}
}
const child = new Child();
console.log(child.field); // "field."
child.showPrivate; // SyntaxError: Private field
※そもそもフィールドとメソッドは異なる
- フィールド:クラスにおけるプロパティ。状態を保持するためのもの
- メソッド:クラスやオブジェクトにおける振る舞いを定義するもの
継承=すべてを複製”ではない!”
- メソッドは親クラスのものを再利用(参照)する(メソッドチェーン)
- フィールドはコンストラクタに複製されたものを参照する
特徴 | フィールド | メソッド |
---|---|---|
役割 | インスタンスの状態を保持するプロパティ | インスタンスの動作を定義する関数 |
格納される場所 | インスタンスごとに格納される | プロトタイプチェーンを通じて共有される |
インスタンスごとの独立性 | インスタンスごとに独立した値を持つ | すべてのインスタンスで共有される |
super
プロパティ
- 子クラスのプロトタイプメソッドから親クラスのプロトタイプメソッドを呼び出す
class Parent {
method() {
console.log("Parent.prototype.method");
}
}
class Child extends Parent {
method() {
console.log("Child.prototype.method");
// `this.method()`だと自分(`this`)のmethodを呼び出して無限ループする
// そのため明示的に`super.method()`を呼ぶことで、Parent.prototype.methodを呼び出す
super.method();
}
}
matome:クラスとプロトタイプとインスタンスの関係
-
クラス
- 構造の設計図のようなもの
- 値と振る舞いを定義されている
-
プロトタイプオブジェクト
- インスタンス間で共通するデータやメソッドを持つオブジェクト
- "クラス"を定義した段階で自動作成される
-
インスタンス
- クラスからできた実体
- 独自のメソッドやフィールドを持てるし、プロトタイプのデータやメソッドを参照し利用したりもできる
例外処理 try...catch
例外発生時の特別な処理をする
try{対象} cach(error){エラー時の処理}
-
finaly{}
- errorに関係なく必ず
try
節の後に実行される処理
- errorに関係なく必ず
try {
console.log("step1"); // step1
// error原因の処理
undefinedFunction();
// error以降は実行されない
console.log("step2");
} catch (error) {
console.log(error.message); // undefinedFunction is not defined
} finally {
console.log("step3"); // step3
}
throw
エラーオブジェクトを投げる
-
throw new Error()
でエラーを明示的にで投げる(Errorは他の基本的なエラーも使える) -
catch
節でエラーを受け取り
try {
// catchのerror変数に投げたエラーが格納される
throw new Error("例外が投げられました");
} catch (error) {
console.log(error.message); // "例外が投げられました"
}
Error()
の部分は他の種類のエラーも用いる場合がある
- Errorオブジェクト
-
Error
: あらゆるエラー -
Reference Error
: 存在しない参照へのアクセス -
Syntax Error
: 存在しない構文 -
Type Error
: 型が期待と異なる
-
Reference Errorの例
// reference error
try {
console.log(x);
} catch (error) {
console.log(error instanceof ReferenceError); // true
console.log(error.name) // "Reference Error"
console.log(error.massage) // "エラーメッセージ"
}
これらの内臓されたエラーをビルドインエラーという
```console.error
-
console.error()
でスタックトレースを表示できる。console.logよりデバッグに有利 - ※スタックトレース:エラーの詳細を追跡したログ
function fn() {
console.log("メッセージ");
console.error("エラーメッセージ");
}
fn();
非同期処理
- 非同期的なタイミングで実行される処理
- ⇔同期処理。タスクの順番通り処理する
- メインスレッドで実行される
setTimeout()
- 指定した時間経過後にコールバック関数を実行する
-
setTimeout(()=> {callBack}, ミリ秒);
- ※一般的にはArrowFanction形式で引数渡す
setTimeout(()=> {
console.log("5秒後に表示");
}, 5000);
非同期によって、処理の完了を待たず次を実行できる
setTimeout(() => {
console.log("task B");
}, 5000);
console.log("task C");
// task A
// task C
// task B
非同期の例外処理
- 非同期処理で発生したエラーは
try...catch
で捕捉できない。
try {
setTimeout(() => {
throw new Error()
}, 5000);
} catch (error) {
// エラー時処理はスルー
console.log("エラーが発生しました")
}
// try内を実行後に非同期処理
// エラーが5秒後に発生。
↓catchの内容はスキップされる
このエラーをキャッチするのであれば以下の様に、setTimeout
内でtry...catch
する
setTimeout(() => {
try {
throw new Error();
} catch (error) {
console.log("エラーが発生しました");
}
}, 5000);
Promeiseオブジェクト
-
関数内などで
return new Promise(resolve, reject) =>{ 非同期処理したい内容 }
-
関数を実行時に
関数名().then(()=>{A}).catch((B)=>{})
- Aには成功時にしたい処理
- Bには失敗時にしたい処理
例:ランダムに処理が成功・失敗するコード
function fetchData() {
return new Promise((resolve, reject) => {
const isSuccess = Math.random() > 0.5; // 成功するか失敗するかをランダムに決める
setTimeout(() => {
if (isSuccess) {
resolve("データの取得に成功しました!"); // 成功時
} else {
reject("データの取得に失敗しました..."); // 失敗時
}
}, 2000); // 2秒後に結果を返す(非同期処理のシミュレーション)
});
}
基本的な書き方は以下
function asyncFunc() {
return new Promise((resolve, reject) => {
// 非同期でしたい処理
// なんらかの結果がでる
if (成功条件) {
resolve(); // これが実行されると成功
} else {
reject(); //これが実行されると失敗
}
});
}
// 呼出し
asyncFunc()
.then(() => {
成功時の処理;
})
.catch(() => {
失敗時の処理;
});
Promiseオブジェクトはresolve()
かreject()
の実行で状態が変化。
実行中・成功・失敗の状態を表す。
-
resolve()
関数が実行されると成功(Fulfilled)状態 -
reject()
関数が実行されると失敗(Rejected)状態 - 成功も失敗もまだない(保留中)(Pending)状態
Promiseチェーン
- Promiseによる非同期処理をメソッドチェーンで繋ぐこと
- 連続で非同期処理を実行できる
Promise.resolve()
// promiseインスタンスが返る
.then(() => {
console.log("成功1");
})
.then(() => {
console.log("成功2");
})
.then(() => {
console.log("成功3");
})
// 成功1
// 成功2
// 成功3
ちなみに失敗が間に挟まると、処理を書かない限りcatch処理成功とし、続くthenは実行される。
Promise.reject()
// promiseインスタンスが返る
.then(() => {
console.log("成功1");
})
.then(() => {
console.log("成功2");
})
.catch(()=>{
console.log("失敗!")
})
.then(() => {
console.log("成功3");
})
// 1, 2はスキップ
// 失敗!
// 成功3
例)ユーザ情報を得るために、メソッドチェーンを利用する
// サーバーからユーザー情報を取得する関数
function getUserInfo(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = { id: userId, name: "Taro", age: 25 }; // ダミーデータ
resolve(user); // 成功時、ユーザー情報を返す
}, 1000); // 1秒後にユーザー情報を返す
});
}
// ユーザーのプロフィールを更新する関数
function updateProfile(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
user.name = "Taro Yamada"; // 名前を変更
resolve(user); // 更新後の情報を返す
}, 1000); // 1秒後に更新された情報を返す
});
}
// 実行例
getUserInfo(1)
.then((user) => {
console.log("ユーザー情報:", user); // ユーザー情報を表示
return updateProfile(user); // ユーザー情報を使ってプロフィールを更新
})
.then((updatedUser) => {
console.log("更新後のユーザー情報:", updatedUser); // 更新後の情報を表示
})
.catch((error) => {
console.error("エラー:", error); // エラーがあれば表示
});