オブジェクトがデフォルトで反復可能でないのはなぜですか?
オブジェクトの繰り返しに関連する質問が常にあります。一般的な解決策は、オブジェクトのプロパティを繰り返し処理し、オブジェクト内の値にそのようにアクセスすることです。これは非常に一般的であるため、オブジェクト自体が反復可能でない理由が不思議に思われます。
ES6 for...of
のようなステートメントは、デフォルトでオブジェクトに使用すると良いでしょう。これらの機能は、{}
オブジェクトを含まない特別な「反復可能なオブジェクト」でのみ使用できるため、使用したいオブジェクトに対してこの作業を行うには、フープを通過する必要があります。
For ... ofステートメントは、繰り返しループ反復可能オブジェクト(Array、Map、Set、argumentsオブジェクトなどを含む)を作成します...
たとえば、ES6 generator function を使用する場合:
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(example)) {
console.log(key);
console.log(value);
for (let [key, value] of entries(value)) {
console.log(key);
console.log(value);
}
}
上記は、Firefoxでコードを実行するときに期待する順序でデータを適切に記録します( ES6 をサポートします)。
デフォルトでは、{}
オブジェクトは反復可能ではありませんが、なぜですか?欠点は、反復可能なオブジェクトの潜在的な利点を上回りますか?これに関連する問題は何ですか?
さらに、{}
オブジェクトは、「配列のような」コレクションや NodeList
、 HtmlCollection
、および arguments
などの「反復可能な」コレクションとは異なるため、配列に変換できません。
例えば:
var argumentsArray = Array.prototype.slice.call(arguments);
または、Arrayメソッドで使用します:
Array.prototype.forEach.call(nodeList, function (element) {})
。
上記の質問に加えて、{}
オブジェクトを反復可能にする方法に関する作業例、特に[Symbol.iterator]
に言及した人からの例を見てみたいと思います。これにより、これらの新しい{}
「反復可能なオブジェクト」がfor...of
のようなステートメントを使用できるようになります。また、オブジェクトを反復可能にすることで、オブジェクトを配列に変換できるのではないかと思います。
以下のコードを試しましたが、TypeError: can't convert undefined to object
が返されます。
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
};
for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error
Object
sは、非常に正当な理由により、Javascriptで反復プロトコルを実装しません。 JavaScriptでオブジェクトプロパティを反復できる2つのレベルがあります。
プログラムレベルでオブジェクトを反復処理するとき、プログラムの構造の一部を調べます。これは反射的な操作です。通常、データレベルで反復される配列型を使用してこのステートメントを説明しましょう。
const xs = [1,2,3];
xs.f = function f() {};
for (let i in xs) console.log(xs[i]); // logs `f` as well
xs
のプログラムレベルを調べました。配列にはデータシーケンスが格納されるため、定期的にデータレベルのみに関心があります。 for..in
は、ほとんどの場合、配列や他の「データ指向」構造との関連で明らかに意味をなしません。これが、ES2015がfor..of
と反復可能なプロトコルを導入した理由です。
関数とプリミティブ型を区別することで、プログラムレベルのデータと単純に区別できるということですか?いいえ、関数はJavascriptのデータでもある可能性があるため:
Array.prototype.sort
は、たとえば、関数が特定のソートアルゴリズムを実行することを想定しています() => 1 + 2
のようなサンクは、遅延評価された値の単なる機能ラッパーですプリミティブ値に加えて、プログラムレベルも表すことができます。
[].length
は、たとえばNumber
ですが、配列の長さを表すため、プログラムドメインに属しますつまり、型をチェックするだけではプログラムとデータレベルを区別できません。
古いプレーンなJavaScriptオブジェクトの反復プロトコルの実装はデータレベルに依存することを理解することが重要です。しかし、これまで見てきたように、データレベルとプログラムレベルの反復との信頼できる区別は不可能です。
Array
sでは、この区別は簡単です。整数のようなキーを持つすべての要素はデータ要素です。 Object
sには同等の機能があります:enumerable
記述子。しかし、これに頼ることは本当にお勧めですか?そうではないと思います! enumerable
記述子の意味があいまいです。
すべてのオブジェクトがコレクションではないため、オブジェクトの反復プロトコルを実装する意味のある方法はありません。
オブジェクトプロパティがデフォルトで反復可能である場合、プログラムとデータレベルは混同されていました。 Javascriptのすべての複合型はプレーンオブジェクトに基づいているため、これはArray
およびMap
にも適用されます。
for..in
、Object.keys
、Reflect.ownKeys
などは、リフレクションとデータ反復の両方に使用できますが、明確な区別は通常不可能です。注意しないと、メタプログラミングと奇妙な依存関係にすぐに陥ってしまいます。 Map
抽象データ型は、プログラムとデータレベルの統合を効果的に終了します。 Map
sがはるかにエキサイティングであっても、Promise
はES2015で最も重要な成果だと思います。
質問は「なぜビルトインオブジェクトの繰り返しがないのか?」
オブジェクト自体に反復可能性を追加すると、意図しない結果が生じる可能性があります。いいえ、順序を保証する方法はありませんが、イテレーターの作成は次のように簡単です。
function* iterate_object(o) {
var keys = Object.keys(o);
for (var i=0; i<keys.length; i++) {
yield [keys[i], o[keys[i]]];
}
}
それから
for (var [key, val] of iterate_object({a: 1, b: 2})) {
console.log(key, val);
}
a 1
b 2
すべてのオブジェクトを簡単にグローバルに反復可能にすることができます。
Object.defineProperty(Object.prototype, Symbol.iterator, {
enumerable: false,
value: function * (){
for(let key in this){
if(this.hasOwnProperty(key)){
yield [key, this[key]];
}
}
}
});
これは最新のアプローチです(chromeカナリアで動作します)
var files = {
'/root': {type: 'directory'},
'/root/example.txt': {type: 'file'}
};
for (let [key, {type}] of Object.entries(files)) {
console.log(type);
}
はいentries
はObjectの一部であるメソッドになりました:)
もっと調べたところ、次のことができるようです
Object.prototype[Symbol.iterator] = function * () {
for (const [key, value] of Object.entries(this)) {
yield {key, value}; // or [key, value]
}
};
あなたは今これを行うことができます
for (const {key, value:{type}} of files) {
console.log(key, type);
}
元の例に戻り、上記のプロトタイプメソッドを使用する場合は、次のようにします。
for (const {key, value:item1} of example) {
console.log(key);
console.log(item1);
for (const {key, value:item2} of item1) {
console.log(key);
console.log(item2);
}
}
技術的には、これは質問に対する答えではありませんなぜ?しかし、BTのコメントを踏まえて、上記のJack Slocumの答えを、オブジェクトを反復可能にするために使用できるものに適合させました。
var iterableProperties={
enumerable: false,
value: function * () {
for(let key in this) if(this.hasOwnProperty(key)) yield this[key];
}
};
var fruit={
'a': 'Apple',
'b': 'banana',
'c': 'cherry'
};
Object.defineProperty(fruit,Symbol.iterator,iterableProperties);
for(let v of fruit) console.log(v);
本来あるべきほど便利ではありませんが、特に複数のオブジェクトがある場合は実行可能です。
var instruments={
'a': 'accordion',
'b': 'banjo',
'c': 'cor anglais'
};
Object.defineProperty(instruments,Symbol.iterator,iterableProperties);
for(let v of instruments) console.log(v);
そして、誰もが意見を述べる権利を持っているので、オブジェクトがまだ反復可能でない理由もわかりません。上記のようにポリフィルできる場合、またはfor … in
を使用できる場合、単純な引数は表示されません。
考えられる提案の1つは、反復可能なのはオブジェクトのtypeであるため、一部の場合にのみ反復可能がオブジェクトのサブセットに制限されている可能性があることです他のオブジェクトはその試みで爆発します。