●enumeratorとは
JavaScriptのenumeratorについて聞いたことがありますか?
enumeratorは、オブジェクトのプロパティを列挙するための便利な機能です。
enumeratorを使えば、オブジェクトのプロパティを順番に取得したり、特定の条件に合致するプロパティだけを取り出したりすることができます。
これはオブジェクト指向プログラミングを学ぶ上でも非常に重要な概念ですね。
今日は、そんなenumeratorについて、実例を交えながらじっくり解説していきたいと思います。
JavaScriptの基本的な文法は理解しているけれど、実践的なコーディングには自信がないという方も、ぜひ最後までお付き合いください。
○enumeratorの基本的な使い方
早速ですが、enumeratorの基本的な使い方について見ていきましょう。
enumeratorを使う一番シンプルな方法は、for…in文を使うことです。
for…in文は、オブジェクトのプロパティを列挙するためのループ構文です。
シンタックスはこんな感じです。
for (variable in object) {
// 処理
}
variableには、プロパティ名が順番に代入されます。
objectには、列挙対象のオブジェクトを指定します。
○サンプルコード1:for…inを使ったenumerator
具体的なコード例を見てみましょう。
次のようなオブジェクトがあるとします。
const fruit = {
apple: 'りんご',
banana: 'バナナ',
orange: 'オレンジ'
};
このfruitオブジェクトのプロパティを列挙するには、for…in文を使って次のように書きます。
for (let name in fruit) {
console.log(name + ': ' + fruit[name]);
}
実行結果は次のようになります。
apple: りんご
banana: バナナ
orange: オレンジ
プロパティ名とプロパティ値が、順番に出力されましたね。
for…in文の中では、プロパティ名がnameに代入され、それを使ってfruit[name]でプロパティ値にアクセスしています。
●enumeratorを使ったオブジェクトの列挙
さて、前回はfor…in文を使ったenumeratorの基本的な使い方を見てきました。
でも正直なところ、もっと簡単にオブジェクトのプロパティを列挙する方法があったら嬉しいですよね。
実は、JavaScriptにはそんな願いを叶えてくれるメソッドがいくつか用意されているんです。
ここからは、そのメソッドを使ってオブジェクトを列挙する方法を、たっぷり見ていきましょう。
みなさん、準備はいいですか?
それでは、さっそくコードを見ながら理解を深めていきましょう!
○サンプルコード2:Object.keysを使ったenumerator
Object.keysメソッドは、オブジェクトの列挙可能なプロパティ名を配列で返してくれます。
先ほどのfruitオブジェクトを使って、実際にObject.keysを使ってみましょう。
const fruit = {
apple: 'りんご',
banana: 'バナナ',
orange: 'オレンジ'
};
const keys = Object.keys(fruit);
console.log(keys);
実行結果はこうなります。
['apple', 'banana', 'orange']
プロパティ名が文字列の配列として取得できましたね。
取得したプロパティ名の配列を使えば、for文でオブジェクトの中身を列挙することができます。
こんな感じです。
for (let i = 0; i < keys.length; i++) {
console.log(keys[i] + ': ' + fruit[keys[i]]);
}
実行結果は、for…in文を使った時と同じになります。
apple: りんご
banana: バナナ
orange: オレンジ
Object.keysメソッドを使えば、プロパティ名の配列を簡単に取得できるので、enumaratorを使う際にはとても便利ですね。
○サンプルコード3:Object.valuesを使ったenumerator
プロパティ名ではなく、プロパティ値の配列が欲しい場合はどうすればいいのでしょうか?
そんな時は、Object.valuesメソッドを使います。見てみましょう。
const values = Object.values(fruit);
console.log(values);
実行結果はこうなります。
['りんご', 'バナナ', 'オレンジ']
Object.keysメソッドとは対照的に、Object.valuesメソッドはプロパティ値の配列を返してくれます。
プロパティ値の配列があれば、for文を使ってそれぞれの値を処理することができますね。
例えばこんな感じです。
for (let i = 0; i < values.length; i++) {
console.log(values[i] + 'は美味しいフルーツです。');
}
実行結果はこのようになります。
りんごは美味しいフルーツです。
バナナは美味しいフルーツです。
オレンジは美味しいフルーツです。
プロパティ値を使った処理を行いたい場合は、Object.valuesメソッドを使うのが便利ですね。
○サンプルコード4:Object.entriesを使ったenumerator
Object.keysとObject.valuesを組み合わせれば、プロパティ名とプロパティ値の両方を取得することができます。
でも、もっとスマートな方法があります。
それが、Object.entriesメソッドです。
Object.entriesメソッドは、オブジェクトのプロパティ名とプロパティ値のペアを配列で返します。実際に使ってみましょう。
const entries = Object.entries(fruit);
console.log(entries);
実行結果は次のようになります。
[
['apple', 'りんご'],
['banana', 'バナナ'],
['orange', 'オレンジ']
]
プロパティ名とプロパティ値のペアが、配列の配列として返ってきますね。
これを使えば、for文で簡単にオブジェクトを列挙できます。
for (let i = 0; i < entries.length; i++) {
const [name, value] = entries[i];
console.log(name + ': ' + value);
}
分割代入を使って、ペアの配列からプロパティ名とプロパティ値を取り出しています。
実行結果は、これまでと同じですね。
apple: りんご
banana: バナナ
orange: オレンジ
プロパティ名とプロパティ値の両方を使った処理を行う場合は、Object.entriesメソッドが役立ちます。
○サンプルコード5:Object.getOwnPropertyNamesを使ったenumerator
最後に、Object.getOwnPropertyNamesメソッドを見ていきましょう。
このメソッドは、Object.keysメソッドと似ていますが、列挙不可能なプロパティ名も含めて配列で返してくれるという特徴があります。
列挙不可能プロパティというのは、enumerableという属性がfalseに設定されたプロパティのことです。
通常のfor…in文やObject.keysメソッドでは列挙されません。
サンプルコードを見てみましょう。
const obj = {
prop1: 'value1',
prop2: 'value2'
};
Object.defineProperty(obj, 'prop3', {
value: 'value3',
enumerable: false
});
const propNames = Object.getOwnPropertyNames(obj);
console.log(propNames);
Object.definePropertyメソッドを使って、prop3を列挙不可能なプロパティとして定義しています。
実行結果は次のようになります。
['prop1', 'prop2', 'prop3']
列挙不可能なprop3も含めて、すべてのプロパティ名が取得できていますね。
Object.getOwnPropertyNamesメソッドは、通常のenumeratorでは列挙されないプロパティも取得したい場合に使うと便利です。
さあ、これでオブジェクトの列挙に使えるメソッドを一通り見てきました。色んな場面で使い分けられるようになったのではないでしょうか。
●enumeratorの応用例
おさらいですが、前回までにenumeratorの基本的な使い方と、オブジェクトの列挙に使えるメソッドについて見てきましたね。
でも、まだまだenumeratorの力を発揮できるシーンはたくさんあります。
ここからは、少し発展的な使い方を見ていきましょう。
enumeratorを使いこなすコツを、一緒にマスターしていきましょう!
○サンプルコード6:特定の条件に合致するプロパティのみ列挙
オブジェクトのプロパティを列挙する際に、特定の条件に合致するプロパティだけを取り出したいことがあります。
そんな時は、if文を使って条件判定を行いながら列挙するのが効果的です。
例えば、次のようなオブジェクトがあるとします。
const person = {
name: '山田太郎',
age: 28,
gender: '男性',
address: '東京都',
hobbies: ['読書', '旅行']
};
ここから、値が文字列型のプロパティだけを列挙したいとしましょう。
その場合、こんなふうにif文を使って条件判定しながらfor…in文を回せばOKです。
for (let key in person) {
if (typeof person[key] === 'string') {
console.log(key + ': ' + person[key]);
}
}
実行結果はこうなります。
name: 山田太郎
gender: 男性
address: 東京都
typeofを使って、プロパティの値の型が文字列かどうかをチェックしています。
文字列型のプロパティだけが列挙されていますね。
このように、条件判定を加えることで、欲しい情報だけを的確に取り出すことができるようになります。
○サンプルコード7:列挙順をカスタマイズ
オブジェクトのプロパティを列挙する際の順番は、通常はオブジェクトに追加された順になります。
でも、アルファベット順やプロパティ値の大小関係など、別の基準で並べ替えたい場合もあるでしょう。
そんな時は、列挙したプロパティを配列に入れて、sortメソッドを使ってソートするのが便利です。
例えば、先ほどのpersonオブジェクトをアルファベット順に列挙するなら、こんな感じです。
const keys = [];
for (let key in person) {
keys.push(key);
}
keys.sort();
for (let i = 0; i < keys.length; i++) {
console.log(keys[i] + ': ' + person[keys[i]]);
}
実行結果は次のようになります。
address: 東京都
age: 28
gender: 男性
hobbies: 読書,旅行
name: 山田太郎
プロパティ名を配列keysに格納し、sortメソッドを使ってアルファベット順にソートしています。
その後、for文でkeysの要素を順番に取り出しながら、personオブジェクトの中身を列挙しています。
sortメソッドにはソート順を決めるコールバック関数を渡すこともできるので、プロパティ値の大小関係などの基準でソートすることも可能です。
列挙順のカスタマイズで、データの見通しを良くしていきましょう。
○サンプルコード8:列挙可能プロパティの判定
オブジェクトのプロパティが列挙可能かどうかを判定したい場合は、propertyIsEnumerableメソッドを使います。
次のコードを見てください。
const obj = { prop1: 'value1' };
Object.defineProperty(obj, 'prop2', { value: 'value2', enumerable: false });
console.log(obj.propertyIsEnumerable('prop1'));
console.log(obj.propertyIsEnumerable('prop2'));
propertyIsEnumerableメソッドは、指定したプロパティが列挙可能な場合にtrueを、そうでない場合にfalseを返します。
実行結果は次のようになります。
true
false
prop1は列挙可能、prop2は列挙不可能なので、ログの内容は納得できますね。
列挙可能性をチェックすることで、オブジェクトのプロパティについてより詳しく知ることができます。
○サンプルコード9:プロトタイプチェーン上のプロパティ列挙
オブジェクトがプロトタイプチェーンを持っている場合、for…in文ではプロトタイプチェーン上のプロパティも列挙されてしまいます。
それを避けるには、hasOwnPropertyメソッドを使って、そのオブジェクト自身が持つプロパティかどうかを確認します。
サンプルコードを見てみましょう。
const proto = { prop1: 'value1' };
const obj = Object.create(proto);
obj.prop2 = 'value2';
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key + ': ' + obj[key]);
}
}
protoオブジェクトをプロトタイプとして、objオブジェクトを作成しています。
objはprop2を持ちますが、for…in文ではprop1も列挙されてしまいます。
そこで、hasOwnPropertyメソッドを使ってobj自身のプロパティかどうかをチェックし、自身のプロパティだけを出力するようにしています。
実行結果は次のようになります。
prop2: value2
意図通り、objが直接持つprop2だけが出力されましたね。
プロトタイプチェーン上のプロパティを意識することで、より正確なプロパティ列挙ができるようになります。
○サンプルコード10:ジェネレーターを使ったenumerator
最後は、ちょっと発展的な内容になりますが、ジェネレーター関数を使ったenumeratorの例を見てみましょう。
ジェネレーター関数は、関数の実行を途中で停止して値を返し、再開できる特殊な関数です。
ジェネレーターを使うと、反復処理を柔軟に制御できます。
次のコードは、ジェネレーターを使ってオブジェクトのプロパティを列挙する例です。
function* enumerate(obj) {
for (let key in obj) {
yield [key, obj[key]];
}
}
const person = {
name: '山田太郎',
age: 28,
gender: '男性'
};
for (let [key, value] of enumerate(person)) {
console.log(key + ': ' + value);
}
enumerateはジェネレーター関数で、yieldを使ってプロパティのキーと値のペアを順番に返します。
ジェネレーターは、for…ofループで反復処理できます。
実行結果は次のようになります。
name: 山田太郎
age: 28
gender: 男性
ジェネレーターを使うことで、反復処理をより細かく制御できるようになります。
例えば、列挙の途中で特定の条件に合致した場合に処理を中断したり、外部から値を渡して処理を制御したりできるようになります。
ジェネレーターは少し難しい概念かもしれませんが、マスターすればよりパワフルなコードが書けるようになりますよ。
●よくあるエラーと対処法
さて、ここまでenumeratorの基本的な使い方から応用例まで、たくさんのサンプルコードを見てきましたね。
でも実際にコードを書いていると、思わぬエラーに遭遇することがあります。
特にenumeratorを使う際によく出くわすエラーとその対処法について、しっかり押さえておきたいところです。
ここではそんな、enumeratorを使う上で陥りがちな落とし穴を、一緒に見ていきましょう。
エラーが出たからといって、すぐに諦める必要はありません。
エラーメッセージをヒントに、原因を特定して適切に対処することが大切です。
一つ一つ確実に理解を深めていけば、必ずエラーを克服できるはずです。
それでは、よく遭遇するエラーとその対策について見ていきましょう。
○プロパティが列挙されない場合の対処法
enumeratorを使ってオブジェクトのプロパティを列挙しようとしても、期待通りのプロパティが列挙されないことがあります。
例えば、次のようなコードを見てみましょう。
const obj = { prop1: 'value1', prop2: 'value2' };
Object.defineProperty(obj, 'prop3', { value: 'value3', enumerable: false });
for (let key in obj) {
console.log(key);
}
ご覧の通り、objオブジェクトにはprop1、prop2、prop3の3つのプロパティがあります。
しかし、実行結果は次のようになります。
prop1
prop2
prop3が列挙されていませんね。
これは、Object.definePropertyメソッドでprop3を定義する際に、enumerableオプションをfalseに設定しているためです。
このように、プロパティの列挙可能性は、プロパティの属性によって制御されています。
列挙されないプロパティがある場合は、プロパティの属性をチェックしてみましょう。
列挙不可能なプロパティも含めて列挙したい場合は、先ほど紹介したObject.getOwnPropertyNamesメソッドを使うのが効果的です。
Object.getOwnPropertyNamesメソッドなら、列挙不可能なプロパティも含めて取得できます。
○列挙中のプロパティ変更による予期せぬ動作の防止
enumeratorでオブジェクトのプロパティを列挙している最中に、プロパティの追加や削除を行うと、予期せぬ動作を引き起こすことがあります。
次のコードを見てみましょう。
const obj = { prop1: 'value1', prop2: 'value2' };
for (let key in obj) {
console.log(key);
if (key === 'prop1') {
obj.prop3 = 'value3';
}
}
console.log(obj);
実行結果は次のようになります。
prop1
prop2
prop3
{ prop1: 'value1', prop2: 'value2', prop3: 'value3' }
列挙中にprop3が追加されているため、追加されたprop3も列挙されてしまっています。
このような動作は、バグの原因になりかねません。
列挙中にオブジェクトを変更したくなる場合は、別の配列にプロパティを保持しておくなどの工夫が必要です。例えばこんな感じです。
const obj = { prop1: 'value1', prop2: 'value2' };
const keys = [];
for (let key in obj) {
keys.push(key);
}
for (let i = 0; i < keys.length; i++) {
console.log(keys[i]);
if (keys[i] === 'prop1') {
obj.prop3 = 'value3';
}
}
console.log(obj);
プロパティ名を配列keysに保持することで、列挙中のオブジェクト変更を避けることができます。
このように、列挙とオブジェクトの変更は分離するのがセオリーです。
○パフォーマンス面での注意点
enumeratorはとても便利な機能ですが、大量のプロパティを列挙する場合はパフォーマンスの低下に注意が必要です。
特にfor…in文は、各プロパティに対してアクセスごとに様々な処理を行うため、プロパティ数が増えるとその分処理時間もかかってしまいます。
パフォーマンスが重要な場面では、Object.keysメソッドやObject.valuesメソッド、Object.entriesメソッドを使って一度配列に変換してから処理するのも手です。
配列なら、単純なfor文でも高速に列挙できます。
const obj = { prop1: 'value1', prop2: 'value2', prop3: 'value3' };
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
console.log(keys[i] + ': ' + obj[keys[i]]);
}
パフォーマンス面での工夫も、スムーズなプログラミングに欠かせないスキルと言えるでしょう。
エラー対処の基本は、エラーメッセージを注意深く読むこと、そしてデバッグに粘り強く取り組むことです。
一見すると難しいエラーも、一つ一つ紐解いていけば必ず解決の糸口が見えてくるはずです。
諦めずに向き合うことが、エンジニアとしての成長に繋がります。
まとめ
JavaScriptのenumeratorは、オブジェクトのプロパティを列挙するための強力な機能です。
for…in文やObject.keysなどを使って、プロパティを順番にアクセスできます。
応用的な使い方やエラー対処法を身につけることで、より柔軟で堅牢なコードが書けるようになるでしょう。
今回学んだ知識を活かして、実践的なプログラミングスキルを磨いていきましょう。
一緒にJavaScriptの探求を続け、エンジニアとして成長していきましょう!