6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2021年11月15日 JavaScript (JS Primer) 配列

コラム:Object.prototypeを継承しないオブジェクト

Objectはすべてのオブジェクトの親になるオブジェクトであるという話でしたが、例外もあるそうです。 以下のコードではObject.prototypeを引数に持たずにnullを渡すと、prototypeは継承しないことが分かります。

// 親がnull、つまり親がいないオブジェクトを作ります。
const obj = Object.create(null);
// Object.prototypeを継承しないため、hasOwnPropertyが存在していません。
console.log(obj.hasOwnProperty); // => undefined

// 以下が一般的に親しまれているオブジェクトです。(createを使う場合)
const obj = Object.create(Object.prototype);

以下のように検証ツールで実行すると、プロパティが存在しないことが確認できます。
Image from Gyazo

上記と同様の挙動はMap使う事で得られるようですが、後の章で詳しく説明があるようです。以下Mapのコード例。

const map = new Map();
// toStringキーは存在しない
console.log(map.has("toString")); // => false

まとめ

この章では、プロトタイプオブジェクトについて学びました。

  • プロトタイプオブジェクトはオブジェクトの作成時に自動的に作成されます。
  • ObjectのプロトタイプオブジェクトにはtoStringなどのプロトタイプメソッドが定義されています。
  • ほとんどのオブジェクトはObject.prototypeを継承することでtoStringメソッドなどを呼び出せます。
  • プロトタイプメソッドとインスタンスメソッドではインスタンスメソッドが優先されます。
  • Object.createメソッドを使うことでプロトタイプオブジェクトを継承しないオブジェクトを作成できます。

配列

配列はJavaScriptの中でもよく使われるオブジェクトです。配列の基本的な特徴は以下の通りです。

  • 値に順序をつけて格納できるオブジェクトです。
  • 配列に格納したそれぞれの値のことを要素、それぞれの要素の位置のことをインデックス(index)と呼びます。
  • インデックスは先頭の要素から0、1、2のように0からはじまる連番となります。
  • JavaScriptにおける配列は可変長です。配列を作成後に配列へ要素を追加したり、配列から要素を削除できます。

存在しないインデックスで配列の要素を取得する

存在しないインデックスで配列の要素を取得しようとすると、以下のようにundefinedが取得できます。配列はオブジェクトです。そのため、配列のインデックスがオブジェクトのプロパティ名と考えると、このような挙動は正しいと言えます。

const array = ["one", "two", "three"];
// `array`にはインデックスが100の要素は定義されていません。
console.log(array[100]); // => undefined

オブジェクトでは、存在しないプロパティにアクセスすると、undefinedを返します。

const obj = {
    "0": "one",
    "1": "two",
    "2": "three",
    "length": 3
};
// obj["100"]は定義されていないため、undefinedが返る
console.log(obj[100]); // => undefined

配列は常にlengthの数だけ要素を持っているとは限らない

const sparseArray = [1,, 3];
console.log(sparseArray.length); // => 3
// 1番目の要素は存在しないため undefined が返る
console.log(sparseArray[1]); // => undefined

このように配列の中で[1,,3]だと2の要素が抜けています。こういった配列を疎な配列(Sparse Arrays) と呼び、全ての要素がある配列を密な配列 と呼びます。

配列と分割代入

分割代入は配列の値やオブジェクトのプロパティを変数として定義し直したい場合に利用できます。 配列の分割代入では、左辺に配列リテラルのような構文で定義したい変数名を書きます。

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

firstにはインデックスが0の要素、secondにはインデックスが1の要素、thirdにはインデックスが2の要素が代入されています。以下のコードのように変数の順番を変えると、2番目に書いたthirdに"three"が正しく代入されていることがわかります。

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

配列にhasOwnPropertyを使う。

オブジェクトが特定のプロパティを持っているか確認するためには、hasOwnPropertyを使います。
配列はオブジェクトの1つなので、実は配列にもhasOwnPropertyが使えます。
要素がundefinedまたは存在しない場合、要素が存在するか区別がつかないのでhasOwnPropertyを使って判定します。

↓ undefinedと存在しないかの区別がつかない説明のコード例

// 要素として`undefined`を持つ密な配列
const denseArray = [1, undefined, 3];
// 要素そのものがない疎な配列
const sparseArray = [1, , 3];
console.log(denseArray[1]); // => undefined
console.log(sparseArray[1]); // => undefined

↓ 存在しているかどうかの判定をしたい場合にhasOwnPropertyを使用したコード例

const denseArray = [1, undefined, 3];
const sparseArray = [1, , 3];
// 要素自体は`undefined`値が存在しています。
console.log(denseArray.hasOwnProperty(1)); // => true
// 要素自体がありません。
console.log(sparseArray.hasOwnProperty(1)); // => false

配列から指定した要素を検索

配列から指定した要素を検索する目的は以下の3つです。

  • その要素のインデックスが欲しい場合
  • その要素自体が欲しい場合
  • その要素が含まれているかという真偽値が欲しい場合

このような検索や判定をするためには、Array.prototypeが提供するメソッドを使います。メソッドについて以下で詳しく説明します。

インデックスを取得

指定した要素が配列のどの位置にあるかを知りたい場合、 Array#indexOfメソッドやArray#findIndexメソッドを利用します。 以下の例では、indexOfメソッドを用いてインデックスを取得しています。

const array = ["A", "B", "C"];
const fetchIndex = array.indexOf("A");
console.log(fetchIndex); // => 0
console.log(array[fetchIndex]); // => "A"

// "ZZZ" という要素はないため `-1` が返されます。
console.log(array.indexOf("ZZZ")); // => -1

indexOfメソッドでは、同じプロパティを持つ異なるオブジェクトを見つけられません。

const obj = { key: "value" };
const array = ["A", "B", obj];
console.log(array.indexOf({ key: "value" })); // => -1
// リテラルは新しいオブジェクトを作るため、異なるオブジェクトだと判定されています。

console.log(obj === { key: "value" }); // => false
// 等価のオブジェクトを検索してインデックスを返します。
console.log(array.indexOf(obj)); // => 2

異なるオブジェクトでも値は同じものを見つけたい場合には、Array#findIndexメソッドが利用できます。

// colorプロパティを持つオブジェクトの配列
const colors = [
    { "color": "red" },
    { "color": "green" },
    { "color": "blue" }
];
// `color`プロパティが"blue"のオブジェクトのインデックスを取得しています。
const indexOfBlue = colors.findIndex((obj) => {
    return obj.color === "blue";
});
console.log(indexOfBlue); // => 2
console.log(colors[indexOfBlue]); // => { "color": "blue" }

条件に一致する要素を取得

findメソッドを使うことで、条件に一致する要素を取得できます。findメソッドの引数には、条件を表すコールバック関数を指定します。条件に一致する要素が存在した場合、その要素を戻り値として返します。

// colorプロパティを持つオブジェクトの配列
const colors = [
    { "color": "red" },
    { "color": "green" },
    { "color": "blue" }
];

// `color`プロパティが"blue"のオブジェクトを取得します
const blueColor = colors.find((obj) => {
    return obj.color === "blue";
});

console.log(blueColor); // => { "color": "blue" }

// 該当する要素がない場合は`undefined`を返します。
const whiteColor = colors.find((obj) => {
    return obj.color === "white";
});

console.log(whiteColor); // => undefined

ここまでのまとめ

  • Array#indexOfメソッドでインデックスを取得できます。
  • Array#findIndexメソッドでインデックスを取得でき、取得したインデックスを配列に指定する事で要素が取得できます。要素を取得したいのかインデックスを取得したいのか明確ではありません。
  • Array#findメソッドで要素が取得できます。

参考

JavaScript Primer 配列