JavaScript 基礎から学び直し
参考文献
JavaScriptとは
JavaScriptは主にウェブブラウザ上で動くプログラミング言語です。
ウェブサイトで操作を行った時に、画面の表示が書き換わったり、サーバーにアクセスしてデータを取得したりする。
JavaScriptはウェブブラウザだけでなく、Node.jsというサーバー側でもアプリケーションを動かす仕組みがある。その他、Iotやデスクトップアプリ、モバイルアプリケーションなどの幅広い場所で使用することができます。
ECMAScriptとは
TL;DR
ウェブブラウザとサーバー側のコードの共通部分。
どちらの環境でも動作する
概要
ECMAScriptという仕様では、どの実行環境でも共通な動作のみが定義されている。
基本的にどの実行環境でも同じ動作をします。
一方、実行環境によって異なる部分もあります。ウェブブラウザではUIを操作するためのJavaScriptの機能が定義されているが、サーバー側(Node.js)にはそれらの機能は不要です。このように、実行環境によって必要な機能は、異なるため、それらの機能は実行環境ごとに定義されている。
ECMAScriptはどの実行環境でも動作する共通部分
意識してなかった
大文字と小文字を区別
NAMEとnameを別々の変数として定義できる
// `name`という名前の変数を宣言
const name = "asa";
// `NAME`という名前の変数を宣言
const NAME = "asa";
文末はセミコロンで区切られる
JavaScriptは、ステートメント(文)ごとに処理していき、文はセミコロンによって区切られる。
ホワイトスペースは挙動に関係ない
スペース、タブ文字などの空白文字(ホワイトスペース)はいくつ配置しても挙動に関係ない。
// 式や文の間にスペースがいくつあっても同じ意味となる
1 + 1;
1 + 1;
strict mode
JavaScriptのコードをより厳格に評価するモード。
"use strict"という文字列をファイル、関数の先頭に書くことで、そのスコープはstrict modeになります
"use strict";
// このコードはstrict modeで実行される
stric modeにすることで、evalやwithといったレガシーな機能や構文を禁止する。
また、開発者が問題を含んだコードに対して早期に例外を投げることで、間違いに気づきやすくなる。
実行コンテキスト
JavaScriptの実行コンテキストとしてScriptとModuleがあります。
Script === デフォルト
Module === strict mode(ES2015で導入)
変数と宣言
JavaScriptには、var
,let
,const
の変数宣言があります。
const
値を再代入できない変数の宣言とその変数が参照する値を定義。
一般的に再代入はバグの要因です。
並行処理、デッドロックなど変数の可変が問題です
注意
constは定数ではない?
プリミティブの値は問題ない。
再代入できない変数ですが、値を変更することは可能(オブジェクト)
let
値の再代入が可能な変数宣言。
constとは異なり、初期値を設定しない宣言も可能。
デフォルトは、undefinedと呼ばれるグローバル変数が代入される
var
letと同じで値の再代入が可能な変数宣言。
varとletの違い??
- 参照できるスコープ(変数の巻き上げ)
function change(){
var x = 3
let y = 3
}
// x=3
console.log(x)
// not defined
console.log(y)
- 同じ名前の変数を再定義できる
// "x"という変数を定義する
var x = 1;
// 同じ名前の変数"x"を定義できる
var x = 2;
// 変数xは2となる
なぜletやconstは追加されたのか?
varを改善するのではなく、新しくletやconstを追加して、var問題を解決する。
後方互換性のため。既存のコードを変更する必要がない
データ型
JavaScriptはプリミティブ型とオブジェクト型の2つのデータ型を持つ
- プリミティブ型
- number
- bigint
- string
- boolean
- null
- undefined
- symbol
- オブジェクト型
- プリミティブ型以外(オブジェクト、Date、配列、関数、クラスなど)
データ型はtypeOfで調べることができる
console.log(typeof true);// => "boolean"
console.log(typeof 42); // => "number"
console.log(typeof 9007199254740992n); // => "bigint"
console.log(typeof "JavaScript"); // => "string"
console.log(typeof Symbol("シンボル"));// => "symbol"
console.log(typeof undefined); // => "undefined"
console.log(typeof null); // => "object"
console.log(typeof ["配列"]); // => "object"
console.log(typeof { "key": "value" }); // => "object"
console.log(typeof function() {}); // => "function"
プリミティブ型はtypeOf演算子でデータ型を返却する。
配列、オブジェクトもobjectというデータ型を返却する。
ただし、関数(function)はオブジェクトの中でも特別扱いされているため、functionとなる。
nullはobject?
nullはobjectになります。これは歴史的な経緯がある。
長くなった...
リテラル
データ型を簡単に定義できる。
プログラム上の数値や文字列など、データ型の値を直接記述できるように構文として定義されたもの。
Boolean
真偽値は下記がリテラル
- true
- false
Number
数値は42のような整数リテラルと3.14159のような浮動小数点数リテラルがある。
倍精度浮動小数では64ビットで数値を表現します。 64ビットのうち52ビットを数字の格納のために使い、11ビットを小数点の位置(小数点第何位)に使い、残りの1ビットはプラスとマイナスの符号です。 そのため、正確に扱える数値の最大値は2^53-1(2の53乗から1引いた値)となります。
10進数
- 複数の数字を組み合わせた際、先頭が0から開始すると8進数として扱われる場合あり。(strict modeの場合は例外が発生する)
- 1,33,22
2進数: 0b(または0B) - 0b0、0b10、0b1010
8進数: 0o(または0O) - 0o は数字のゼロと小文字アルファベットのo
- 0o644、0o777
16進数: 0x(または0X) - アルファベットの大文字・小文字の違いは値には影響しません
- 0x30A2、0xEEFF
浮動小数リテラル
- 3.141519のドットを挟む
- 2e8を含む値
BigInt
最大値は2^53-1(2の53乗から1引いた値)以上の値
Number.MAX_SAFE_INTEGER => 9007199254740991
数値リテラルは2^53-1(2の53乗から1引いた値)を表現する場合、計算と間違った結果となる場合があった。この問題を解決するため、BigIntは誕生した
Numeric Separators
ES2021から、数値リテラル内の区切り文字として_を追加できるNumeric Separatorsがサポートされています。
1_000_000_000_000;
Numeric SeparatorsはNumber,BigIntのみ利用できる
String
文字列リテラルは3種類の表現方法があります。
console.log("文字列"); // => "文字列"
console.log('文字列'); // => "文字列"
console.log(`文字列`); // => "文字列"
null
nullリテラルはnull値を返却するリテラル。nullは「値がない」ということを表現できる
オブジェクトリテラル
JavaScriptにとってオブジェクトはあらゆるものの基礎です。
const obj = {}; // 中身が空のオブジェクトを作成
オブジェクトは作成と同時に中身を定義できる
const obj = {
"key": "value" // キー値と値
};
オブジェクトはドット(.)とブラケット([])で参照できる
const obj = {
"key": "value"
};
// ドット記法
console.log(obj.key); // => "value"
// ブラケット記法
console.log(obj["key"]); // => "value"
注意
keyが数値の場合、ドットは使用できない
// プロパティ名は文字列の"123"
const object = {
"123": "value"
};
// OK: ブラケット記法では、文字列として書くことができる
console.log(object["123"]); // => "value"
// NG: ドット記法では、数値からはじまる識別子は利用できない
object.123
配列リテラル
[]に値をカンマ区切りで囲み、表現する
const emptyArray = []; // 空の配列を作成
const array = [1, 2, 3]; // 値を持った配列を作成
正規表現リテラル
JavaScriptの正規表現は//スラッシュで正規表現のパターンを囲む
const numberRegExp = /\d+/; // 1文字以上の数字にマッチする正規表現
// `numberRegExp`の正規表現が文字列"123"にマッチするかをテストする
console.log(numberRegExp.test("123")); // => true
演算子
演算子は、他のプログラミング言語でも使用する。演算子の対象をオペランドと呼ぶ。
JavaScriptでも特に挙動が理解しにくい暗黙的な型変換
知っている演算子は割愛
- プラス演算子(+)
- マイナス演算子(-)
- 乗算演算子(*)
- 除算演算子(/)
- 剰余演算子(%)
- 大なり演算子(>)
- 大なりイコール演算子(>=)
- 小なり演算子(<)
- 小なりイコール演算子(<=)
べき乗演算子
2つの数値のべき乗を求める
console.log(2 ** 4) // => 16
NaN
not a numberは数値ではないがNumber型の値
Number.isNaNメソッドでNaN判定を行える
厳密等価演算子
厳密等価演算子は、左右のオペランドを比較。同じ型、同じ値の場合のみtrueを返す。
console.log(1 === 1); // => true
console.log(1 === "1"); // => false
オブジェクトの場合は、同じ参照の場合のみtrueを返す
// {} は新しいオブジェクトを作成しているˆ
const objA = {};
const objB = {};
// 生成されたオブジェクトは異なる参照となる
console.log(objA === objB); // => false
// 同じ参照を比較している場合
console.log(objA === objA); // => true
ビット論理積
論理積はビットごとのAND演算した結果を返す
console.log(15 & 9); // => 9
// 同じ位の各ビット同士をAND演算する(上位の`0`は省略)
// 1111
// 1001
// ----
// 1001
console.log(0b1111 & 0b1001); // => 0b1001
ビット論理和
論理和はビットごとのOR演算した結果を返す
console.log(15 | 9);
// 同じ位の各ビット同士をOR演算する(上位の`0`は省略)
// 1111
// 1001
// ----
// 1111
console.log(0b1111 | 0b1001); // => 0b1111
排他的論理和
排他的論理はビットごとのXOR演算した結果を返す
console.log(15 ^ 9); // => 6
// 同じ位の各ビット同士をXOR演算する(上位の`0`は省略)
// 1111
// 1001
// ----
// 0110
console.log(0b1111 ^ 0b1001); // => 0b0110
ビット否定
単項演算子の否定演算子(~)は各ビットを反転させる。
~xのようにxをビット否定演算子で演算した結果は、-(x + 1)となります。
console.log(~15); // => -16
左シフト演算子
数値であるnumをbitの数だけ左へシフトします。
console.log( 9 << 2); // => 36
console.log(0b1001 << 2); // => 0b10_0100
右シフト演算子
数値であるnumをbitの数だけ右へシフトします(右は1詰)
console.log((-9) >> 2); // => -3
// 1111_1111_1111_1111_1111_1111_1111_0111 >> 2
// => 1111_1111_1111_1111_1111_1111_1111_1101
右0シフト演算子
数値であるnumをbitの数だけ右へシフトします(右は0詰)
console.log((-9) >>> 2); // => 1073741821
// 1111_1111_1111_1111_1111_1111_1111_0111 >>> 2
// => 0011_1111_1111_1111_1111_1111_1111_1101
分割代入
Reactで多用する構文。
配列やオブジェクトの値を複数の変数に代入することができる。
分割代入は、短縮記法の1つ。
配列の場合
const array = [1, 2];
// aには`array`の0番目の値、bには1番目の値が代入される
const [a, b] = array;
console.log(a); // => 1
console.log(b); // => 2
オブジェクトの場合
const obj = {
"key": "value"
};
// プロパティ名`key`の値を、変数`key`として定義する
const { key } = obj;
console.log(key); // => "value"
AND演算子
AND演算子は左辺の値がtrueであれば右辺の評価結果を返却する
一方で、左辺の値がfalseであれば、左辺の評価結果を返却する
// 左辺がtrueなので、右辺は評価される
true && console.log("このコンソールログは実行されます");
// 左辺がfalseなので、右辺は評価されない
false && console.log("このコンソールログは実行されません");
値が決まった時点でそれ以上評価しないことを短絡評価という。
左辺がfalsyの値の場合、右辺を評価しない。
falsy
- false
- undefined
- null
- 0
- ""
- 0n
- Nan
OR演算子
右辺の値がtrueであれば右辺の値を返却し、左辺の値を評価しない
一方で右辺の値がfalsyであれば、左辺の値を返却する
// 左辺がtrueなので、左辺の値が返される
console.log(true || "右辺の値"); // => true
// 左辺がfalseなので、右辺の値が返される
console.log(false || "右辺の値"); // => "右辺の値"
Nullish coalescing演算子
左辺の値がnullishであるならば、右辺の評価結果を返します。
nullishとはnullまたはundefinedの値です。
// 左辺がnullishであるため、右辺の値の評価結果を返す
console.log(null ?? "右辺の値"); // => "右辺の値"
console.log(undefined ?? "右辺の値"); // => "右辺の値"
// 左辺がnullishではないため、左辺の値の評価結果を返す
console.log(true ?? "右辺の値"); // => true
console.log(false ?? "右辺の値"); // => false
console.log(0 ?? "右辺の値"); // => 0
console.log("文字列" ?? "右辺の値"); // => "文字列"
暗黙的な型変換
暗黙的な型変換とは
ある処理において、その処理の過程で明示的では無い型変換が行われること
明示的な型変換
JavaScriptはBooleanコンストラクタ関数を使用し、任意の値を真偽値に変換する
Boolean("string"); // => true
Boolean(1); // => true
Boolean({}); // => true
Boolean(0); // => false
Boolean(""); // => false
Boolean(null); // => false
数値->文字列
String("str"); // => "str"
String(true); // => "true"
String(null); // => "null"
String(undefined); // => "undefined"
String(Symbol("シンボルの説明文")); // => "Symbol(シンボルの説明文)"
// プリミティブ型ではない値の場合
String([1, 2, 3]); // => "1,2,3"
String({ key: "value" }); // => "[object Object]"
String(function() {}); // "function() {}"
オブジェクトは意味のある文字列に変換することはできない。
オブジェクトを文字列に変換したい場合は、JSON.stringfifyメソッドなどの適切な方法がある。Stringコンストラクタ関数の変換はプリミティブ型に留めるべき
シンボル->文字列
プラス演算子を文字列に利用した場合、文字列の結合を優先します。
ES2015で追加されたプリミティブ型であるシンボルは暗黙的に型変換できない。
"文字列と" + Symbol("シンボルの説明"); /
Stringのコンストラクタを使用することで型変換できる
"文字列と" + String(Symbol("シンボルの説明")); // => "文字列とSymbol(シンボルの説明)"
文字列->数値
文字列から数値へ明示的に変換するにはNumberコンストラクタ関数が利用できる
// ユーザー入力を文字列として受け取る
const input = window.prompt("数字を入力してください", "42");
// 文字列を数値に変換する
const num = Number(input);
console.log(typeof num); // => "number"
console.log(num); // 入力された文字列を数値に変換したもの
関数宣言
JavaScriptは、関数を定義するときにfunctionキーワードを使います。
functionから始まるステートメントは関数宣言と呼びます。
// 関数宣言
function 関数名(仮引数1, 仮引数2) {
// 関数が呼び出されたときの処理
// ...
return 関数の返り値;
}
// 関数呼び出し
const 関数の結果 = 関数名(引数1, 引数2);
console.log(関数の結果); // => 関数の返り値
関数の構成要素
- 関数名
- 仮引数
- 関数の中身
- 関数の戻り値
宣言した関数は、関数名()で呼び出せます。引数がある場合は、関数名(引数1,引数2)
呼び出し時の引数が少ない時
仮引数よりも呼び出し時の引数が少ない場合は、余った仮引数にはundefinedが入る
デフォルト引数
ES2015で導入された、仮引数に対応する引数が渡されていない場合に、デフォルトで代入する値を指定できる
function echo(x = "デフォルト値") {
return x;
}
console.log(echo(1)); // => 1
console.log(echo()); // => "デフォルト値
可変長引数(残余引数)
関数において引数の数が固定でなく、任意の個数を受け取りたい時がある。
引数の数を固定ではなく、可変長する引数
function fn(...args) {
// argsは、渡された引数が入った配列
console.log(args); // => ["a", "b", "c"]
}
fn("a", "b", "c");
配列を展開して関数の引数に渡す
spread構文とは配列の前に...をつけた構文のこと。関数に配列を展開したものが引数として渡される
function fn(x, y, z) {
console.log(x); // => 1
console.log(y); // => 2
console.log(z); // => 3
}
const array = [1, 2, 3];
// Spread構文で配列を引数に展開して関数を呼び出す
fn(...array);
// 次のように書いたのと同じ意味
fn(array[0], array[1], array[2]);
関数の引数と分割代入
分割代入とは、オブジェクトや配列からプロパティを取り出し、変数として定義する
// オブジェクトの場合
function printUserId(user) {
console.log(user.id); // => 42
}
const user = {
id: 42
};
printUserId(user);
//配列の場合
function names([name1,name2]){
return name1 + "と" + name2 + "は友達です"
}
names(["b","a"])
関数はオブジェクト
JavaScriptは、関数は関数オブジェクトと呼ばれ、オブジェクトの一種です。
関数は、ただのオブジェクトとは異なり、()をつけることでまとめた処理を呼び出せます。
関数が値として扱えることを、ファーストクラスファンクション(第一級関数)と呼びます。
function fn() {
console.log("fnが呼び出されました");
}
// 関数`fn`を`myFunc`変数に代入している
const myFunc = fn;
myFunc();
関数式
関数を値として、変数に代入している式のこと。
関数式は関数名を省略できます。なぜなら、変数名で参照できるためです。
const 変数名 = function (){
return 戻り値
}
関数式では、名前を持たない関数(無名関数)を変数に代入できます。
Arrow Function
=>を使い、無名関数を定義する構文です。
const 変数名 = () => {
// 関数を呼び出したときの処理
// ...
return 関数の返す値;
};
Arrow Functionの書き方はいくつかのパターンがある。
const fnA = () => { /* 仮引数がないとき */ };
const fnB = (x) => { /* 仮引数が1つのみのとき */ };
const fnC = x => { /* 仮引数が1つのみのときは()を省略可能 */ };
const fnD = (x, y) => { /* 仮引数が複数のとき */ };
// 値の返し方
2つは同じ意味
const mulA = (x) => { return x * x };
const mulB = x => x * x ;
Arrow Functionの特徴
- thisのスコープ
- functionに比べて短くかける
- newできない
- arguments変数を参照できない
同じ名前の関数宣言は上書きされる
関数宣言で定義した関数は、関数の名前のみ区別されます。
複数回同じ名前の関数を宣言した場合には、後ろで宣言された関数によって上書きされます。
function fn(x) {
return `最初の関数 x: ${x}`;
}
function fn(x, y) {
return `最後の関数 x: ${x}, y: ${y}`;
}
console.log(fn(2, 10)); // => "最後の関数 x: 2, y: 10"
コールバック関数
関数の引数に無名関数を渡すこと。
引数として渡される関数のことをコールバック関数と呼びます。一方でコールバック関数を引数として使う関数やメソッドを高階関数と呼びます。
const array = [1, 2, 3];
const output = (value) => {
console.log(value);
};
array.forEach(output);
// 次のように実行しているのと同じ
// output(1); => 1
// output(2); => 2
// output(3); => 3
メソッド
オブジェクトのプロパティである関数をメソッドと呼びます。
const obj = {
method: function(){
// functionキーワードでのメソッド
return "this"
},
method2: () => {
// Arrow Functionでのメソッド
return "this"
},
// メソッドの短縮記法
method3(){
return "this"
}
}
//メソッドの呼び出し
obj.method2()
文と式
式
1+1,42,fooといった変数を式と呼びます。
式の特徴として、式を評価すると結果の値を得ることができる。この結果の値を評価値と呼びます。
評価した結果を変数に代入することができれば、式である
// 1という式の評価値を表示
console.log(1); // => 1
// 1 + 1という式の評価値を表示
console.log(1 + 1); // => 2
// 式の評価値を変数に代入
const total = 1 + 1;
// 関数式の評価値(関数オブジェクト)を変数に代入
const fn = function() {
return 1;
};
// fn() という式の評価値を表示
console.log(fn()); // => 1
文
処理する1ステップが1つの文と言える。
if文やfor文は文と呼びます。
const isTrue = true;
// isTrueという式がif文の中に出てくる(式)
if (isTrue) {
}
一方でif文などは式に慣れない。if文を変数に代入することができないため。
式文
一方で、式は文になれます。文となった式を式文と呼びます。基本的に文が書ける場所には指揮をかけます。
ブロック文
文を{と}で囲んだ部分をブロックと言います。
文の末尾はセミコロンが必要ですが、ブロックの末尾には不要。
// if文とブロック文の組み合わせ
if (true) {
console.log("文1");
console.log("文2");
}
関数宣言と関数式
関数を定義する方法として、functionから始まる関数宣言と変数に関数を代入する関数式があります
// learn関数を宣言する関数宣言文
function learn() {
}
// 関数式をread変数へ代入
const read = function() {
};
関数宣言と関数式でブロック文の末尾にセミコロンを必要性が変化します。
関数宣言の場合は、ブロックで終わるため、セミコロンは不要です。
しかし、関数式は無名関数のため、変数を宣言する文の一部です。そのため、末尾にセミコロンが必要になります。
function fn() {}
// fn(式)の評価値を代入する変数宣言の文
const read = fn;
条件分岐
割愛
- if
- switch
学び
switch文の評価は厳密等価演算子を使用している
ループと反復処理
割愛
- for文
- while(条件式がtrueの間繰り返す)
- do-while(必ず1度、実行する)
- some(配列のなかにコールバック関数で指定した値が存在すれば,trueを戻り値として返す)
forEach
for文と同じように反復処理を行う。for文のように条件式無く、必ず配列のすべての要素を反復処理します。
JavaScriptは、関数がファーストクラスであるため、その場で作った無名関数を引数として、渡せる。
引数として、渡される関数をコールバック関数と呼びます。
break文
break文は処理中の文から抜けて、次の文へ移行する制御文。whie,do-while,forで使用可能。
filter文
配列から特定の値のみをまとめた新しい配列を返却する。
function isEven(num) {
return num % 2 === 0;
}
const array = [1, 5, 10, 15, 20];
console.log(array.filter(isEven)); // => [10, 20]
for...in
課題あり。
JavaScriptは何らかの親オブジェクトを継承しています。そのため、for...in文は、対象となるオブジェクトのプロパティ以外にも参照する。そのため、オブジェクトが自身が持っていないプロパティを列挙することがある。
オブジェクトを列挙したい場合は、Object.keysメソッド、Object.valuesメソッド、Object.entriesメソッドを利用できる。
const obj = {
"a": 1,
"b": 2,
"c": 3
};
Object.keys(obj).forEach(key => {
const value = obj[key]
console.log(value)
})
for...of
JavaScriptでは、Symbol.iteratorという特別な名前のメソッドを実装したオブジェクトをiterableと呼びます。iterableオブジェクトは、for...ofで反復処理できます。
for( value of values){
}
Arrayはiterableオブジェクトです。
for...of文で、配列から値を取り出して、反復処理を行えます!
その他、Stringオブジェクト、TypedArray、Map、Set、DOM NodeListなどもiterableオブジェクトです。
オブジェクト
オブジェクトはプロパティの集合です。プロパティとは、キーと値が対になったものです。
キーは、文字列かSymbolを指定でき、値は任意のデータを定義できます。
また、1つのオブジェクトに複数のプロパティを持てるため、1つのオブジェクトで多種多様な値を表現できます。
関数や配列もオブジェクトです。JavaScriptは、あらゆるオブジェクトのもとになるObjectが存在します。
ObjectはECMAScriptの仕様として定義されている。どの環境でも使用できる。
オブジェクトリテラルは{}で表現できます。
const obj = {
// キーとプロパティ
num : 1
}
const object = {
// キー: 値
my-prop: "value" // NG
};
// キーの中にハイフンが含まれる場合、タブルクォートで囲む必要がある
const object = {
// キー: 値
"my-prop": "value" // NG
};
ES2015年から、プロパティ名とキー名は同じであれば、プロパティを省略できるようになりました
const name = "名前";
// `name`というプロパティ名で`name`の変数を値に設定したオブジェクト
const obj = {
name
};
console.log(obj); // => { name: "名前" }
この省略記法は、分割代入やモジュールにおいても共通した表現です。
{}はObjectのインスタンスオブジェクト
ObjectはJavaScriptのビルトインオブジェクトです。{}リテラルもビルトインオブジェクトから作成されています。オブジェクトリテラル以外にもnewを使用したインスタンスオブジェクトも作成できます。
これは、オブジェクトリテラルを使用した例と同じ結果になります。
const obj = new Object()
オブジェクトリテラルの方が明らかに簡潔で、プロパティの初期値も指定できるため、newを使用する利点はありません。
プロパティへのアクセス
オブジェクトのプロパティにアクセスする方法は2通りです。
1つは、ドットを使用した方法
const obj = {
name : 'test'
}
obj.name
ドットの場合、数値やハイフン付きのキー値はアクセスできません
もう1つは、ブランケットを使用した方法です
const obj = {
key: "value",
123: 456,
"my-key": "my-value"
};
console.log(obj["key"]); // => "value"
// プロパティ名が数字からはじまる識別子も利用できる
console.log(obj[123]); // => 456
// プロパティ名は暗黙的に文字列に変換されているため、次も同じプロパティを参照している
console.log(obj["123"]); // => 456
// プロパティ名にハイフンを含む識別子も利用できる
console.log(obj["my-key"]); // => "my-value"
ブランケット記法だと、識別子の命名規則とは関係なく、任意の文字列をプロパティ名として指定できる。ただし、プロパティ名は暗黙的に型変換されている。
基本的には、ドット、必要に応じてブランケットを使用する。
同じオブジェクトのプロパティに何度もアクセスする場合に、何度もオブジェクト.プロパティ名と書くと
冗長になりやすいです。そのため、短い名前で利用できるように、プロパティを変数として定義し直すことができます。
const languages = {
ja: "日本語",
en: "英語"
};
const { ja, en } = languages;
console.log(ja); // => "日本語"
console.log(en); // => "英語"
プロパティの追加
オブジェクトは一度作成するとミュータブルな値となります。そのため、作成したオブジェクトのプロパティを変更することができます。
const languages = {
ja: "日本語",
en: "ああ"
};
languages.en = "英語"
ドット記法は、変数の識別子として利用可能なもの。
一方、ブランケット記法は
- 変数
- 変数の識別子として扱えない文字列
- Symbol
をプロパティ値として、使用できる
const key = "key-string";
const obj = {};
// `key`の評価結果 "key-string" をプロパティ名に利用
obj[key] = "value of key";
// 取り出すときも同じく`key`変数を利用
console.log(obj[key]); // => "value of key"
ブランケット記法を用いたプロパティ定義は、オブジェクトリテラルでも使用できます。
computed property names
const key = "key-string";
// Computed Propertyで`key`の評価結果 "key-string" をプロパティ名に利用
const obj = {
[key]: "value"
};
console.log(obj[key]); // => "value"
JavaScriptのオブジェクトは、作成後にプロパティの変換ができるmutablieの特性を持ちます。
function changeProperty(obj) {
obj.key = "value";
// いろいろな処理...
}
const obj = {};
changeProperty(obj); // objのプロパティを変更している
console.log(obj.key); // => "value"
プロパティの初期値以外を代入すると、そのオブジェクトが保有しているプロパティがわかりにくくなります。そのため、できる限り作成後にプロパティを追加しない方が良いでしょう。
プロパティの削除
オブジェクトのプロパティを削除するにはdelete演算子を利用します。
削除したいプロパティをdelete演算子の右辺に指定して、プロパティを削除できます!
const obj = {
key1: "value1",
key2: "value2"
};
// key1プロパティを削除
delete obj.key1;
// key1プロパティが削除されている
console.log(obj); // => { "key2": "value2" }
constで定義したオブジェクトはプロパティを変更可能
constはプリミティブ型の値についてはイミュータブルな型になりますが、オブジェクト型はミュータブルの型になります。そのため、値のプロパティの変更が可能です。constは変数への再代入不可
const obj ={}
obj = {name:''} // TypeError
オブジェクトのプロパティの変更を防止するには、Object.freezeメソッドを使用することで、プロパティを読み取り専用にできる。(TypeScriptのreadonly)
ただし、Object.freezeメソッドはstrict modeでしか機能しかないので注意してください。
"use strict"
const object = Object.freeze({ key: "value" });
// freezeしたオブジェクトにプロパティを追加や変更できない
object.key = "value"; // => TypeError: "key" is read-only
プロパティの存在を確認する
オブジェクトは存在しないプロパティにアクセスしたときにundefinedを返します。
存在しないプロパティかを見分ける方法として
- undefinedの比較
- inメソッド
- object.hasown
- object.protype.hasOwnProperty
undefinedの比較
シンプルにkeyプロパティがundefineであるかを比較する
const obj = {
key: "value"
};
if(obj.key !== undefined){
console.log("keyプロパティの値はundefinedではない")
}
しかし、プロパティの初期値にundefinedを設定した場合に、プロパティが存在することを区別できない問題があります。
const obj = {
key: undefined
};
// `key`プロパティの値が`undefined`である場合
if (obj.key !== undefined) {
// この行は実行されません
}
このような問題があるため、in演算子かObject.hasOwnメソッドを使用します。
プロパティの存在を確認:in演算子
in演算子は、指定したオブジェクト上に指定したプロパティがあるかを判定し、真偽値を返します。
"プロパティ" in オブジェクト;
in演算子の例
const obj = {key:undefined};
if("key" in obj){
console.log("key")
}
// key
しかし、in演算子はプロトタイプオブジェクトで指定されたメソッドも検索します。
そのため、オブジェクトに存在しない、メソッドも参照します。
プロパティの存在を確認(ES2022)
Object.hasOwn静的メソッドは、対象のオブジェクトが指定したプロパティを持っているかを判断できます。このObject.hasOwn静的メソッドの引数には、オブジェクトとキーを渡します
const obj = {key:undefined};
if(Object.hasOwn(obj,'key'){
console.log("objはkeyプロパティを持っている")
}
Object.hasOwnはプロトタイプオブジェクトのメソッドを参照していません。
Object.hasOwnはES2022に誕生したメソッドで、それより前はObject.prototype.hasOwnPropertyメソッドというよく似たメソッドが利用されていた。
hawOwnPropertyメソッドは、Object.hasOwn静的メソッドとよく似ていますが、オブジェクトのインスタンスから呼び出す点が異なります。
Optional Chaining演算子
ES2020では、ネストしたプロパティへのアクセスと存在の確認を簡単に行う構文としてOptional chaining演算子(?.)が導入された。
const obj = {
a: {
b: "objのaプロパティのbプロパティ"
}
};
console.log(obj?.a?.b) // obj****
console.log(obj?.key) // undefined
また、ブランケット記法を使用した方法もあります。
const languages = {
ja: {
hello: "こんにちは!"
},
en: {
hello: "Hello!"
}
};
const langJapanese = "ja";
const langKorean = "ko";
const messageKey = "hello";
// Optional chaining演算子(`?.`)とブラケット記法を組みわせた書き方
console.log(languages?.[langJapanese]?.[messageKey]); // => "こんにちは!"
// `languages`に`ko`プロパティが定義されていないため、`undefined`を返す
console.log(languages?.[langKorean]?.[messageKey]); // => undefined
toString
オブジェクトのtoStringメソッドは、オブジェクト自身を文字列化します。
Stringコンストラクタ関数を使用することで文字列化もできます。
しかし、この2つには違いがあります。
Stringコンストラク関数は、引数に渡されたオブジェクトのtoStringメソッドを呼び出しています。そのため、Stringコンストラクタ関数とtoStringメソッドは同じ結果になる
const obj = { key : "value"}
// 同じメソッドを呼んでいる
console.log(obj.toString());=> "[object Object]"
console.log(String(obj)); // => "[object Object]"
オブジェクトにtoStringを再定義するとわかる
// 独自のtoStringメソッドを定義
const customObject = {
toString() {
return "custom value";
}
};
console.log(String(customObject)); // => "custom value"
オブジェクトのプロパティは文字列化される
オブジェクトのプロパティへアクセスするときに、指定したプロパティ名は暗黙的に文字列に変換されます。ブランケット記法では、オブジェクトをプロパティに指定することは可能ですが、これは意図した結果になりません。
その理由は、objectを文字列にすると[object Object]という文字列になるためです。
下記はオブジェクトをプロパティ名として指定した際に意図した結果を得られない例です。
const obj = {};
const keyObject1 = { a: 1 };
const keyObject2 = { b: 2 };
// どちらも同じプロパティ名("[object Object]")に代入している
obj[keyObject1] = "1";
obj[keyObject2] = "2";
console.log(obj); // { "[object Object]": "2" }
唯一の例外として、Symbolだけは文字列化されずにオブジェクトのプロパティ名として扱えます。
const obj = {}
const symbolKey1 = Symbol("シンボル1")
const symbolKey2 = Symbol("シンボル2")
obj[symbolKey1] = "1"
obj[symbolKey2] = "2"
console.log(obj[symbolKey1]); // => "1"
console.log(obj[symbolKey2]); // => "2"
基本的にはオブジェクトのプロパティ名は文字列に暗黙的に変換されます。また、Mapというビルトインオブジェクトはオブジェクトをキーとして扱えます。
オブジェクトの静的メソッド
ビルトインオブジェクトに定義されているメソッド。(toString,hasOwn)
オブジェクトの列挙
オブジェクトはプロパティの集合です。そのオブジェクトのプロパティを列挙する方法として3つの方法があります。
- Object.keysメソッド:オブジェクトのプロパティ名の配列を返します
- Object.valuesメソッド:オブジェクトの値の配列を返します
- Object.entriesメソッド:オブジェクトのプロパティ名と値の配列の配列を返す
const obj = {
"one": 1,
"two": 2,
"three": 3
};
console.log(Object.keys(obj)) //["one","two","three"]
console.log(Object.values(obj)) // [1,2,3]
console.log(Object.entries(obj)) // [["one",1],["two",2],["three",3]]
オブジェクトのマージと複製
ES2015にオブジェクトは、あるオブジェクトを別のオブジェクトに代入(assign)できます。このメソッドを使うことで、オブジェクトの複製やオブジェクト同士のマージができます。
const objectA = { a: "a" };
const objectB = { b: "b" };
// 第一引数がターゲット、第二引数が値
const merged = Object.assign({},objectA,objectB);
第一引数には既存のオブジェクトを指定することもできます。
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = Object.assign(objectA, objectB);
console.log(merged); // => { a: "a", b: "b" }
// `objectA`が変更されている
console.log(objectA); // => { a: "a", b: "b" }
console.log(merged === objectA); // => true
空のオブジェクトを第一引数にするのが基本。既存のobjectにマージしてしまうと参照しているオブジェクトも全てプロパティが更新される。
オブジェクトのspread構文でのマージ
ES2018で、オブジェクトのマージを行うspread構文が追加されました。
オブジェクトspread構文は、Object.assignとは異なり、必ず新しいオブジェクトを作成します。
const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = {
...objectA,
...objectB
}
プロパティ名が被った場合は、後ろにあるオブジェクトのプロパティ名が優先されます。
オブジェクトの複製
JavaScriptはオブジェクトを複製する関数は用意されていません。しかし、新しく空のオブジェクトを作成し、そこへ既存のオブジェクトをコピーすれば、それはオブジェクトの複製をしていると言えます。
注意点としてObject.assginメソッドはオブジェクトのプロパティを浅くコピーする点です。shallow copyは直下にあるプロパティだけをコピーするということです。そのプロパティがオブジェクトである場合は、参照元と同じメモリを参照します。
const shallowClone = (obj) => {
return Object.assign({}, obj);
};
const obj = {
level: 1,
nest: {
level: 2
},
};
const cloneObj = shallowClone(obj);
// `nest`プロパティのオブジェクトは同じオブジェクトのままになる
console.log(cloneObj.nest === obj.nest); // => true
逆にプロパティの値までも再起的に複製してコピーすることをdeep copyと言います。
deep copyは、再起的にshallow copyすることで実現できます。
JavaScriptのビルトインメソッドは浅い実装のみを提供し、深い実装は提供しないことが多いです。言語として最低限の機能を提供し、より複雑な機能はユーザー側で実装するという形式を取るためです。
プロトタイプオブジェクト
オブジェクトリテラルで空オブジェクトを定義したときにも、toString,hawOwnなどのメソッドを呼び出すことができました。これらのオブジェクトに組み込まれたメソッドをビルトインメソッドと呼びます。
Array,String,FunctionなどのオブジェクトはすべてObjectを継承している。
正確には、Object.prototypeプロパティに定義されたprototypeオブジェクトを継承している。
Objectのprototypeオブジェクトは、全てのオブジェクトから利用できるメソッドを提供するベースオブジェクトとも言えます。