web-dev-qa-db-ja.com

オブジェクトがJavaScriptで反復不可能なのはなぜですか?

オブジェクトがデフォルトで反復可能でないのはなぜですか?

オブジェクトの繰り返しに関連する質問が常にあります。一般的な解決策は、オブジェクトのプロパティを繰り返し処理し、オブジェクト内の値にそのようにアクセスすることです。これは非常に一般的であるため、オブジェクト自体が反復可能でない理由が不思議に思われます。

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 をサポートします)。

output of hacky for...of

デフォルトでは、{}オブジェクトは反復可能ではありませんが、なぜですか?欠点は、反復可能なオブジェクトの潜在的な利点を上回りますか?これに関連する問題は何ですか?

さらに、{}オブジェクトは、「配列のような」コレクションや NodeListHtmlCollection 、および 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
66
boombox

Objectsは、非常に正当な理由により、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オブジェクトの反復プロトコルの実装はデータレベルに依存することを理解することが重要です。しかし、これまで見てきたように、データレベルとプログラムレベルの反復との信頼できる区別は不可能です。

Arraysでは、この区別は簡単です。整数のようなキーを持つすべての要素はデータ要素です。 Objectsには同等の機能があります:enumerable記述子。しかし、これに頼ることは本当にお勧めですか?そうではないと思います! enumerable記述子の意味があいまいです。

結論

すべてのオブジェクトがコレクションではないため、オブジェクトの反復プロトコルを実装する意味のある方法はありません。

オブジェクトプロパティがデフォルトで反復可能である場合、プログラムとデータレベルは混同されていました。 Javascriptのすべての複合型はプレーンオブジェクトに基づいているため、これはArrayおよびMapにも適用されます。

for..inObject.keysReflect.ownKeysなどは、リフレクションとデータ反復の両方に使用できますが、明確な区別は通常不可能です。注意しないと、メタプログラミングと奇妙な依存関係にすぐに陥ってしまいます。 Map抽象データ型は、プログラムとデータレベルの統合を効果的に終了します。 Mapsがはるかにエキサイティングであっても、PromiseはES2015で最も重要な成果だと思います。

7
user6445533

質問は「なぜビルトインオブジェクトの繰り返しがないのか?」

オブジェクト自体に反復可能性を追加すると、意図しない結果が生じる可能性があります。いいえ、順序を保証する方法はありませんが、イテレーターの作成は次のように簡単です。

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
7
user663031

すべてのオブジェクトを簡単にグローバルに反復可能にすることができます。

Object.defineProperty(Object.prototype, Symbol.iterator, {
    enumerable: false,
    value: function * (){
        for(let key in this){
            if(this.hasOwnProperty(key)){
                yield [key, this[key]];
            }
        }
    }
});
4
Jack Slocum

これは最新のアプローチです(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);
}

edit2

元の例に戻り、上記のプロトタイプメソッドを使用する場合は、次のようにします。

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);
    }
}
1
Chad Scira

技術的には、これは質問に対する答えではありませんなぜ?しかし、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であるため、一部の場合にのみ反復可能がオブジェクトのサブセットに制限されている可能性があることです他のオブジェクトはその試みで爆発します。

0
Manngo