JavaScript 条件分岐 / オブジェクト / プロトタイプオブジェクト / 文字列

条件分岐

条件分岐

真偽値以外の値の場合、その値を暗黙的に真偽値へ変換してから、条件式として判定します。

真偽値へ変換するとtrueとなる値の種類は多いため、逆に変換した結果がfalseとなる値を覚えるのが簡単です。 次の値は真偽値へと変換するとfalseとなるため、これらはfalsyな値と呼ばれます(「暗黙的な型変換」の章を参照)。

falsy

  • false
  • undefined
  • null
  • 0
  • 0n
  • NaN
  • “”(空文字列)

オブジェクト

オブジェクト

  • オブジェクトはプロパティの集合
  • プロパティとは名前(キー)と値(バリュー)が対になったものです。
  • 配列や関数などもオブジェクトの一種
  • JavaScriptには、あらゆるオブジェクトの元となるObjectというビルトインオブジェクトが存在する。

ポイント1

ES2015からは、プロパティ名と値に指定する変数名が同じ場合は{ name }のように省略して書けます。 次のコードは、プロパティ名nameに変数nameを値にしたプロパティを設定しています。

const name = "名前";
// `name`というプロパティ名で`name`の変数を値に設定したオブジェクト
const obj = {
    name
};
console.log(obj); // => { name: "名前" }

ポイント2

{}はObjectのインスタンスオブジェクト

オブジェクトリテラルのほうが明らかに簡潔で、プロパティの初期値も指定できるため、new Object()を使う利点はありません。

// プロパティを持たない空のオブジェクトを作成
// = `Object`からインスタンスオブジェクトを作成
const obj = new Object();
console.log(obj); // => {}

ポイント3

プロパティへのアクセス

ドット記法(.)を使う方法とブラケット記法([])があります。

const obj = {
    key: "value"
};
// ドット記法で参照
console.log(obj.key); // => "value"
// ブラケット記法で参照
console.log(obj["key"]); // => "value"

ドット記法(.)では、プロパティ名が変数名と同じく識別子の命名規則を満たす必要があります

obj.key; // OK
// プロパティ名が数字から始まる識別子は利用できない
obj.123; // NG
// プロパティ名にハイフンを含む識別子は利用できない
obj.my-prop; // NG

ポイント4

[ES2015] オブジェクトと分割代入

オブジェクトのプロパティを変数として定義し直すときには、分割代入(Destructuring assignment)が利用できる。

const languages = {
    ja: "日本語",
    en: "英語"
};
const ja = languages.ja;
const en = languages.en;
console.log(ja); // => "日本語"
console.log(en); // => "英語"

ポイント5

プロパティの存在を確認する

JavaScriptでは存在しないプロパティへアクセスした場合に例外が発生しません。 プロパティ名を間違えた場合に単にundefinedという値を返すため、間違いに気づきにくいという問題があります。

const obj = {};
console.log(obj.notFound); // => undefined

in演算子

in演算子は、指定したオブジェクト上に指定したプロパティがあるかを判定し真偽値を返します。

const obj = { key: undefined };
// `key`プロパティを持っているならtrue
if ("key" in obj) {
    console.log("`key`プロパティは存在する");
}

Object.hasOwn

Object.hasOwn静的メソッドは、対象のオブジェクトが指定したプロパティを持っているかを判定できます。 このObject.hasOwn静的メソッドの引数には、オブジェクトとオブジェクトが持っているかを確認したいプロパティ名を渡します。

const obj = { key: undefined };
// `obj`が`key`プロパティを持っているならtrueとなる
if (Object.hasOwn(obj, "key")) {
    console.log("`obj`は`key`プロパティを持っている");
}

ポイント6

ES2020ではネストしたプロパティの存在確認とアクセスを簡単に行う構文としてOptional chaining演算子(?.)が導入されました。 Optional chaining演算子(?.)は、ドット記法(.)の代わりに?.をプロパティアクセスに使います。

Optional chaining演算子(?.)は左辺のオペランドがnullish(nullまたはundefined)の場合は、それ以上評価せずにundefinedを返します。一方で、プロパティが存在する場合は、そのプロパティの評価結果を返します。

if (widget.window !== undefined && widget.window.title !== undefined) {
    console.log(`ウィジェットのタイトルは${widget.window.title}です`);
} else {
    console.log("ウィジェットのタイトルは未定義です");
}

Optional chaining演算子(?.)を使うと

const title = widget?.window?.title ?? "未定義";
console.log(`ウィジェットのタイトルは${title}です`);

Optional chaining演算子(?.)はブラケット記法([])と組み合わせることもできます

const title = widget?.['window']?.['title'] ?? "未定義";
console.log(`ウィジェットのタイトルは${title}です`);

ポイント7

オブジェクトはプロパティの集合です。 そのオブジェクトのプロパティを列挙する方法として、次の3つの静的メソッドがあります。

  • Object.keysメソッド: オブジェクトのプロパティ名の配列にして返す
  • Object.valuesメソッド[ES2017]: オブジェクトの値の配列にして返す
  • Object.entriesメソッド[ES2017]: オブジェクトのプロパティ名と値の配列の配列を返す
const obj = {
    "one": 1,
    "two": 2,
    "three": 3
};
// `Object.keys`はキーを列挙した配列を返す
console.log(Object.keys(obj)); // => ["one", "two", "three"]
// `Object.values`は値を列挙した配列を返す
console.log(Object.values(obj)); // => [1, 2, 3]
// `Object.entries`は[キー, 値]の配列を返す
console.log(Object.entries(obj)); // => [["one", 1], ["two", 2], ["three", 3]]

オブジェクトをループしたいときなど、よく使います。

ポイント8

[ES2018] オブジェクトのspread構文でのマージ

オブジェクトのspread構文は、Object.assignとは異なり必ず新しいオブジェクトを作成します。 なぜならspread構文はオブジェクトリテラルの中でのみ記述でき、オブジェクトリテラルは新しいオブジェクトを作成するためです。

Object.assign はあまり使わない。spread構文でのマージで十分。

プロトタイプオブジェクト

プロトタイプオブジェクト

Objectには、他のArray、String、Functionなどのオブジェクトとは異なる特徴があります。 それは、他のオブジェクトはすべてObjectを継承しているという点です。

正確には、ほとんどすべてのオブジェクトはObject.prototypeプロパティに定義されたprototypeオブジェクトを継承しています。 prototypeオブジェクトとは、すべてのオブジェクトの作成時に自動的に追加される特殊なオブジェクトです。 Objectのprototypeオブジェクトは、すべてのオブジェクトから利用できるメソッドなどを提供するベースオブジェクトとも言えます。

  • Object.prototypeに定義されているtoStringメソッドなどは、インスタンス作成時に自動的に継承される
  • インスタンスからprototypeオブジェクト上に定義されたメソッドを参照できる仕組みをプロトタイプチェーンと呼ぶ。
  • Object.hasOwn静的メソッドは、指定したオブジェクト自体が指定したプロパティを持っているかを判定します。 一方、in演算子はオブジェクト自身が持っていなければ、そのオブジェクトの継承元であるprototypeオブジェクトまで探索して持っているかを判定します。 つまり、in演算子はインスタンスに実装されたメソッドなのか、プロトタイプオブジェクトに実装されたメソッドなのかを区別しません。
const obj = {};
// `obj`というオブジェクト自体に`toString`メソッドが定義されているわけではない
console.log(Object.hasOwn(obj, "toString")); // => false
// `in`演算子は指定されたプロパティ名が見つかるまで親をたどるため、`Object.prototype`まで見にいく
console.log("toString" in obj); // => true

  • Object.createメソッドはES5から導入、Object.createメソッドはObject.create(null)というイディオムで、一部ライブラリなどでMapオブジェクトの代わりとして利用されていました。しかし、ES2015からは本物のMapが利用できるため、Object.create(null)をMapの代わりに利用する必要はありません。 Mapについては「Map/Set」の章で詳しく紹介します。

配列

配列

ポイント1

[ES2022] Array.prototype.at

  • ES2022では、相対的なインデックスの値を指定して配列の要素へアクセスできるArray.prototype.atメソッドが追加されました。
  • Arrayのatメソッドは、配列[インデックス]とよく似ていますが、引数には相対的なインデックスの値を引数として渡せます。 .at(0)や.at(1)などのように0以上のインデックスを渡した場合は、配列[インデックス]と同じく指定した位置の要素へアクセスできます。 一方で、.at(-1)のようにマイナスのインデックスを渡した場合は、末尾から数えた位置の要素へアクセスできます。
const array = ["a", "b", "c"];
//
console.log(array.at(0)); // => "a"
console.log(array.at(1)); // => "b"
// 後ろから1つ目の要素にアクセス
console.log(array.at(-1)); // => "c"
// -1は、次のように書いた場合と同じ結果
console.log(array[array.length - 1]); // => "c"

ポイント2

オブジェクトが配列かどうかを判定する

const obj = {};
const array = [];
console.log(Array.isArray(obj)); // => false
console.log(Array.isArray(array)); // => true

console.log(typeof obj); // => "object"
console.log(typeof array); // => "object"

ポイント3

[ES2015] 配列と分割代入

const array = ["one", "two", "three"];
const [first, second, third] = array;
console.log(first);  // => "one"
console.log(second); // => "two"
console.log(third);  // => "three"

ポイント4

[コラム] undefinedの要素と未定義の要素の違い

Object.hasOwn静的メソッドを使うことで、配列オブジェクトに対して指定したインデックスに要素自体が存在するかを判定できます。

const denseArray = [1, undefined, 3];
const sparseArray = [1, , 3];
// 要素自体は存在し、その値が`undefined`
console.log(Object.hasOwn(denseArray, 1)); // => true
// 要素自体が存在しない
console.log(Object.hasOwn(sparseArray, 1)); // => false

ポイント5

指定範囲の要素を取得

配列から指定範囲の要素を取り出す方法としてArrayのsliceメソッドが利用できます。 sliceメソッドは、第一引数の開始位置から第二引数の終了位置(終了位置の要素は含まない)までの範囲を取り出した新しい配列を返します。 第二引数は省略でき、省略した場合は配列の末尾の要素まで含んだ新しい配列を返します。

const array = ["A", "B", "C", "D", "E"];
// インデックス1から4まで(4の要素は含まない)の範囲を取り出す
console.log(array.slice(1, 4)); // => ["B", "C", "D"]
// 第二引数を省略した場合は、第一引数から末尾の要素までを取り出す
console.log(array.slice(1)); // => ["B", "C", "D", "E"]
// マイナスを指定すると後ろからの数えた位置となる
console.log(array.slice(-1)); // => ["E"]
// 第一引数と第二引数が同じ場合は、空の配列を返す
console.log(array.slice(1, 1)); // => []
// 第一引数 > 第二引数の場合、常に空配列を返す
console.log(array.slice(4, 1)); // => []

sliceメソッドと引数の関係を図にすると次のようになります。

 +-----+-----+-----+-----+-----+
 | "A" | "B" | "C" | "D" | "E" |
 +-----+-----+-----+-----+-----+
 0     1     2     3     4     5
-5    -4    -3    -2    -1

ポイント6

[ES2019] 配列をフラット化

Arrayのflatメソッド[ES2019]を使うことで、多次元配列をフラットな配列に変換できます。 引数を指定しなかった場合は1段階のみのフラット化ですが、引数に渡す数値でフラット化する深さを指定できます。 配列をすべてフラット化する場合には、無限を意味するInfinityを値として渡すことで実現できます。

const array = [[["A"], "B"], "C"];
// 引数なしは 1 を指定した場合と同じ
console.log(array.flat()); // => [["A"], "B", "C"]
console.log(array.flat(1)); // => [["A"], "B", "C"]
console.log(array.flat(2)); // => ["A", "B", "C"]
// すべてをフラット化するには Infinity を渡す
console.log(array.flat(Infinity)); // => ["A", "B", "C"]

ポイント7

JavaScriptの配列には破壊的なメソッドと非破壊的メソッドが混在しています。

メソッド名 返り値
Array.prototype.pop 配列の末尾の値
Array.prototype.push 変更後の配列のlength
Array.prototype.splice 取り除かれた要素を含む配列
Array.prototype.reverse 反転した配列
Array.prototype.shift 配列の先頭の値
Array.prototype.sort ソートした配列
Array.prototype.unshift 変更後の配列のlength
Array.prototype.copyWithin[ES2015] 変更後の配列
Array.prototype.fill[ES2015] 変更後の配列

ポイント8

reduceメソッドに渡したコールバック関数は配列の要素数である3回呼び出され、それぞれ次のような結果になります。

 - accumulator currentValue returnした値
1回目の呼び出し 0 1 0 + 1
2回目の呼び出し 1 2 1 + 2
3回目の呼び出し 3 3 3 + 3

ポイント9

[コラム] Array-likeオブジェクト

  • Array-likeオブジェクトの例としてarguments があります。
  • Array-likeオブジェクトか配列なのかを判別するにはArray.isArrayメソッドを利用できます。 Array-likeオブジェクトは配列ではないので結果は常にfalseとなります。
function myFunc() {
    console.log(Array.isArray([1, 2, 3])); // => true
    console.log(Array.isArray(arguments)); // => false
}
myFunc("a", "b", "c");
  • Array.fromメソッド[ES2015]を使うことでArray-likeオブジェクトを配列に変換して扱うことができます。
function myFunc() {
    // Array-likeオブジェクトを配列へ変換
    const argumentsArray = Array.from(arguments);
    console.log(Array.isArray(argumentsArray)); // => true
}
myFunc("a", "b", "c");

文字列

文字列

  • 文字列リテラルには”(ダブルクォート)、’(シングルクォート)、`(バッククォート)の3種類があります。
  • “(ダブルクォート)と’(シングルクォート)に意味的な違いなし
  • ES2015では、テンプレートリテラル `(バッククォート)が追加された。

ポイント1

[ES2022] String.prototype.at

配列と同じようにインデックスを渡してその位置の文字へアクセスできる。

const str = "文字列";
console.log(str.at(0)); // => "文"
console.log(str.at(1)); // => "字"
console.log(str.at(2)); // => "列"
console.log(str.at(-1)); // => "列"

ポイント2

文字列を構成するCode Unitをhex値(16進数)にして表示

const str = "アオイ";
// それぞれの文字をCode Unitのhex値(16進数)に変換する
// toStringの引数に16を渡すと16進数に変換される
console.log(str.charCodeAt(0).toString(16)); // => "30a2"
console.log(str.charCodeAt(1).toString(16)); // => "30aa"
console.log(str.charCodeAt(2).toString(16));  // => "30a4"

逆にCode Unitをhex値(16進数)から文字へと変換

const str = String.fromCharCode(
    0x30a2, // アのCode Unit
    0x30aa, // オのCode Unit
    0x30a4  // イのCode Unit
);
console.log(str); // => "アオイ"

ポイント3

正規表現リテラルとRegExpコンストラクタの違い

ソースコードのロード時に正規表現のパターンが評価されるため、 次のようにmain関数を呼び出していなくても構文エラー(SyntaxError)が発生します。

// 正規表現リテラルはロード時にパターンが評価され、例外が発生する
function main() {
    // `[`は対となる`]`を組み合わせる特殊文字であるため、単独で書けない
    const invalidPattern = /[/;
}

// `main`関数を呼び出さなくても例外が発生する

RegExpコンストラクタは実行時に正規表現のパターンが評価されるため、 main関数を呼び出すことで初めて構文エラー(SyntaxError)が発生します。

// `RegExp`コンストラクタは実行時にパターンが評価され、例外が発生する
function main() {
    // `[`は対となる`]`を組み合わせる特殊文字であるため、単独で書けない
    const invalidPattern = new RegExp("[");
}

// `main`関数を呼び出すことで初めて例外が発生する
main();

ポイント4

正規表現によるインデックスの取得

数字が3つ連続しているかを検索し、該当した箇所のインデックスを返しています。

const str = "ABC123EFG";
const searchPattern = /\d{3}/;
console.log(str.search(searchPattern)); // => 3

ポイント5

マッチした文字列の取得

matchメソッドは正規表現のgフラグなしのパターンで検索した場合、最初にマッチしたものが見つかった時点で検索が終了します。

matchメソッドは正規表現のgフラグありのパターンで検索した場合、マッチしたすべての文字列を含んだ配列を返します。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// gフラグありでは、すべての検索結果を含む配列を返す
const resultsWithG = str.match(alphabetsPattern);
console.log(resultsWithG.length); // => 2
console.log(resultsWithG[0]); // => "ABC"
console.log(resultsWithG[1]); // => "DE"
// indexとinputはgフラグありの場合は追加されない
console.log(resultsWithG.index); // => undefined
console.log(resultsWithG.input); // => undefined

ES2020では、正規表現のgフラグを使った繰り返しマッチする場合においても、それぞれマッチした文字列ごとの情報を得るためのStringのmatchAllが追加されています。 matchAllメソッドは、マッチした結果をIteratorで返します。

正規表現のgフラグを使った繰り返しマッチを行う場合には、matchメソッドではなくmatchAllメソッドを利用します。 また、matchAllメソッドはgフラグなしの正規表現はサポートしていないため、gフラグなしの正規表現を渡した場合は例外が発生します。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// matchAllはIteratorを返す
const matchesIterator = str.matchAll(alphabetsPattern);
for (const match of matchesIterator) {
    // マッチした要素ごとの情報を含んでいる
    console.log(`match: "${match[0]}", index: ${match.index}, input: "${match.input}"`);
}
// 次の順番でコンソールに出力される
// match: "ABC", index: 0, input: "ABC あいう DE えお"
// match: "DE", index: 8, input: "ABC あいう DE えお"

StringのmatchAllメソッドは、ES2020で導入されたメソッドです。 それまでは、RegExpのexecメソッドというStringのmatchメソッドによく似た挙動をするメソッドを利用して、StringのmatchAllメソッド相当の表現を実装していました。