JavaScriptでオブジェクトを反復できないのはなぜですか?


87

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

オブジェクトの反復に関連する質問が常にあります。一般的な解決策は、オブジェクトのプロパティを反復し、その方法でオブジェクト内の値にアクセスすることです。これは非常に一般的であるため、オブジェクト自体が反復可能ではないのはなぜか疑問に思います。

ES6のようなステートメントは、for...ofデフォルトでオブジェクトに使用すると便利です。これらの機能は、{}オブジェクトを含まない特別な「反復可能なオブジェクト」でのみ使用できるため、使用するオブジェクトに対してこれを機能させるには、フープを実行する必要があります。

for ... ofステートメントは、反復可能なオブジェクト (Array、Map、Set、argumentsオブジェクトなどを含む)を反復処理するループを作成します。

たとえば、ES6ジェネレーター関数を使用します

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をサポート)でコードを実行したときに期待する順序でデータを適切にログに記録します。

ハッキーの出力...の

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

ために加えて、{}オブジェクトは「アレイ状」コレクションとは異なっているような「反復可能オブジェクト」NodeListHtmlCollectionおよびarguments、それらが配列に変換することができません。

例えば:

var argumentsArray = Array.prototype.slice.call(arguments);

または配列メソッドで使用します:

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

1
持っている何かSymbol.iteratorプロパティが反復可能です。したがって、そのプロパティを実装する必要があります。オブジェクトが反復可能でない理由の1つの考えられる説明は、すべてがオブジェクトであるため(もちろんプリミティブを除く)、これはすべてが反復可能であることを意味する可能性があります。ただし、関数または正規表現オブジェクトを反復処理するとはどういう意味ですか?
Felix Kling 2015

7
ここであなたの実際の質問は何ですか?ECMAが決定を下したのはなぜですか?
スティーブベネット

3
オブジェクトにはプロパティの順序が保証されていないので、予測可能な順序を持つと予想される反復可能の定義から外れるのではないかと思います。
jfriend00 2015

2
「なぜ」に対する信頼できる答えを得るには、esdiscuss.orgで
Felix Kling

1
@ FelixKling-その投稿はES6に関するものですか?「ECMAScriptの次のバージョン」は時間の経過とともにうまく機能しないため、おそらく編集して、話しているバージョンを指定する必要があります。
jfriend00 2015

回答:


42

これを試してみます。私はECMAと提携しておらず、彼らの意思決定プロセスを把握していないため、ECMAが何もしていない理由を明確に言うことはできません。しかし、私は私の仮定を述べて、私のベストショットを撮ります。

1.for...ofそもそもなぜ構成を追加するのですか?

JavaScriptにfor...inは、オブジェクトのプロパティを反復するために使用できる構造がすでに含まれています。ただし、これは実際にはforEachループはありません。これは、オブジェクトのすべてのプロパティを列挙し、単純な場合にのみ予測どおりに機能する傾向があるためです。

より複雑な場合(アレイを正しく使用するために必要なセーフガードによって使用が推奨されないか、完全に難読化される傾向があるアレイを含む)に分類されます。(とりわけ)を使用してそれを回避することができますが、それは少し不格好でエレガントではありません。 for...inhasOwnProperty

したがって、私の仮定は、for...of構成に関連する欠陥に対処するために構成が追加されており、for...in物事を繰り返すときに、より優れた有用性と柔軟性を提供することです。人々は、一般的にあらゆるコレクションに適用でき、あらゆる可能なコンテキストで適切な結果を生成できるループfor...inとして扱う傾向がありforEachますが、それは起こりません。for...ofループはそれを修正します。

また、既存のES5コードをES6で実行し、ES5で実行したのと同じ結果を生成することが重要であると想定しますfor...in。そのため、たとえば、構成の動作に重大な変更を加えることはできません。

2.どのように機能しfor...ofますか?

リファレンスドキュメントはこの部分に便利です。具体的には、オブジェクトがプロパティをiterable定義している場合、そのオブジェクトが考慮されSymbol.iteratorます。

プロパティ定義は、コレクション内のアイテムを1つずつ返し、フェッチするアイテムがまだあるかどうかを示すフラグを設定する関数である必要があります。一部のオブジェクトタイプには事前定義された実装が提供されておりfor...of単純にイテレータ関数に委任することは比較的明らかです。

このアプローチは、独自のイテレータを提供するのが非常に簡単になるため、便利です。以前は存在しなかったプロパティの定義に依存しているため、このアプローチは実際的な問題を提示した可能性があると言えます。ただし、意図的に探しに行かない限り、新しいプロパティは本質的に無視されるため、そうではないことがわかります。for...inキーなどとしてループに表示されることはありません)。そうではありません。

実用的な問題はさておき、すべてのオブジェクトを新しい事前定義されたプロパティで開始すること、または「すべてのオブジェクトはコレクションである」と暗黙的に言うことは、概念的に物議を醸すと考えられている可能性があります。

3.オブジェクトがデフォルトでiterable使用for...ofされないのはなぜですか?

私の推測では、これは次の組み合わせです。

  1. すべてのオブジェクトiterableをデフォルトで作成すると、以前は存在しなかったプロパティが追加されるため、またはオブジェクトが(必然的に)コレクションではないため、受け入れられないと見なされた可能性があります。Felixが指摘しているように、「関数または正規表現オブジェクトを反復処理することはどういう意味ですか?」
  2. 単純なオブジェクトはfor...in、を使用してすでに反復できます。また、組み込みのイテレーター実装が既存のfor...in動作とは異なる/優れた方法で何を実行できたかは明らかではありません。したがって、#1が間違っていて、プロパティの追加が許容できる場合でも、それは有用であるとは見なされていない可能性があります。
  3. オブジェクトを作成したいユーザーiterableは、Symbol.iteratorプロパティを定義することで簡単に作成できます。
  4. ES6仕様は、マップタイプも提供します。これ iterableデフォルトであり、プレーンオブジェクトをとして使用するよりも他のいくつかの小さな利点がありますMap

リファレンスドキュメントには、#3の例もあります。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

for (var value of myIterable) {
    console.log(value);
}

オブジェクトは簡単に作成できiterable、を使用してすでに反復for...in可能であり、デフォルトのオブジェクトイテレータが何をすべきかについて明確な合意がない可能性が高いことを考えると(それが行うことは何とか異なることを意味する場合for...in)、それは合理的と思われますオブジェクトがiterableデフォルトで作成されていないほど十分です。

サンプルコードは、次を使用して書き直すことができることに注意してくださいfor...in

for (let levelOneKey in object) {
    console.log(levelOneKey);         //  "example"
    console.log(object[levelOneKey]); // {"random":"nest","another":"thing"}

    var levelTwoObj = object[levelOneKey];
    for (let levelTwoKey in levelTwoObj ) {
        console.log(levelTwoKey);   // "random"
        console.log(levelTwoObj[levelTwoKey]); // "nest"
    }
}

...またはiterable、次のような方法でオブジェクトを作成することもできます(または、代わりにに割り当てることですべてのオブジェクトを作成できます)。iterableObject.prototype[Symbol.iterator]

obj = { 
    a: '1', 
    b: { something: 'else' }, 
    c: 4, 
    d: { nested: { nestedAgain: true }}
};

obj[Symbol.iterator] = function() {
    var keys = [];
    var ref = this;
    for (var key in this) {
        //note:  can do hasOwnProperty() here, etc.
        keys.push(key);
    }

    return {
        next: function() {
            if (this._keys && this._obj && this._index < this._keys.length) {
                var key = this._keys[this._index];
                this._index++;
                return { key: key, value: this._obj[key], done: false };
            } else {
                return { done: true };
            }
        },
        _index: 0,
        _keys: keys,
        _obj: ref
    };
};

あなたはここでそれで遊ぶことができます(Chromeでは、少なくとも):http//jsfiddle.net/rncr3ppz/5/

編集

そして、更新された質問に答えて、はい、ES6のspread演算子iterableを使用して、を配列に変換することができます。

ただし、これはまだChromeで機能していないようです。または、少なくともjsFiddleで機能させることはできません。理論的には、次のように単純にする必要があります。

var array = [...myIterable];

obj[Symbol.iterator] = obj[Symbol.enumerate]最後の例でやってみませんか?
ベルギ2015

@ Bergi-ドキュメントにそれが表示されなかったため(そしてここで説明さているプロパティが表示さていないため)。イテレータを明示的に定義することを支持する1つの議論は、必要に応じて特定の反復順序を簡単に適用できるようにすることです。ただし、反復順序が重要でなく(またはデフォルトの順序で問題ない場合)、1行のショートカットが機能する場合は、より簡潔なアプローチを採用しない理由はほとんどありません。
aroth 2015

おっと、[[enumerate]]よく知られたシンボル(@@ enumerate)ではなく、内部メソッドです。私はしなければならないでしょうobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
ベルギ2015

実際の議論のプロセスが十分に文書化されている場合、これらすべての推測はどのように使用されますか?「したがって、私の仮定は、for ... in構造に関連する欠陥に対処するために、for ... of構造が追加されているということです」と言うのは非常に奇妙です。いいえ。これは、あらゆるものを反復処理する一般的な方法をサポートするために追加されたものであり、反復可能オブジェクト自体、ジェネレーター、マップとセットなど、幅広い新機能のセットの一部です。これは、オブジェクトのプロパティ間for...in反復するという別の目的を持つ、の置き換えまたはアップグレードを意味するものではありません。

2
すべてのオブジェクトがコレクションであるとは限らないことを再度強調する良い点。オブジェクトは非常に便利だったため、長い間そのように使用されてきましたが、最終的には実際にはコレクションではありません。それMapが今のところ私たちが持っているものです。
Felix Kling 2015

9

Object■非常に正当な理由により、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この区別は重要ではありません。整数のようなキーを持つすべての要素は、データ要素です。Object■同等の機能があります:enumerable記述子。しかし、これに頼ることは本当に賢明ですか?そうではないと思います!enumerable記述子の意味があいまいすぎます。

結論

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

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

for..inObject.keysReflect.ownKeys等両方反射およびデータの反復に使用することができ、明確な区別は、定期的に不可能です。注意しないと、すぐにメタプログラミングと奇妙な依存関係になってしまいます。Map抽象データ型は、効果的にプログラムやデータレベルのconflatingを終了します。MapたとえPromisesがはるかにエキサイティングであったとしても、私はES2015で最も重要な成果であると信じています。


3
+1、「すべてのオブジェクトがコレクションであるとは限らないため、オブジェクトの反復プロトコルを実装する意味のある方法はありません」と思います。それを合計。
チャーリーシュリーサー2018

1
それは良い議論ではないと思います。オブジェクトがコレクションではない場合、なぜそれをループしようとしているのですか?それをしない問題ではそうでないものを反復処理しようとすることはありませんので、必ずしもすべてのオブジェクトは、コレクションであること。
BT

実際、すべてのオブジェクトコレクションであり、コレクションが一貫しているかどうかを判断するのは言語次第ではありません。配列とマップは、無関係な値を収集することもできます。重要なのは、オブジェクトの用途に関係なく、任意のオブジェクトのキーを反復処理できるため、オブジェクトの値を反復処理することから一歩離れているということです。配列(またはその他のコレクション)の値を静的に入力する言語について話している場合は、そのような制限について話すことはできますが、JavaScriptについて話すことはできません。
マンゴ

すべてのオブジェクトがコレクションではないというその議論は意味がありません。イテレータには(コレクションを反復する)目的が1つしかないことを前提としています。オブジェクトのデフォルトのイテレータは、それらのプロパティが何を表しているかに関係なく(コレクションまたはその他のもの)、オブジェクトのプロパティのイテレータになります。Manngoが言ったように、オブジェクトがコレクションを表していない場合、それをコレクションのように扱わないのはプログラマーの責任です。デバッグ出力のためにオブジェクトのプロパティを繰り返したいだけなのかもしれません。コレクション以外にもたくさんの理由があります。
jfriend 0020年

8

質問は「なぜ組み込みのオブジェクト反復がないのか?」だと思います。

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

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

1
ありがとうラザブロ。質問を修正しました。[Symbol.iterator]これらの意図しない結果を拡張できるかどうかだけでなく、を使用した例を見てみたいと思います。
ラジカセ2015

4

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

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

3
ネイティブオブジェクトにメソッドをグローバルに追加しないでください。これは、あなたとあなたのコードを使用する人をお尻に噛み付くようなひどい考えです。
BT

2

これは最新のアプローチです(クロームカナリアで機能します)

var files = {
    '/root': {type: 'directory'},
    '/root/example.txt': {type: 'file'}
};

for (let [key, {type}] of Object.entries(files)) {
    console.log(type);
}

はいentriesはオブジェクトの一部であるメソッドになりました:)

編集

それをもっと調べた後、あなたは次のことができるようです

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);
    }
}

2

私もこの質問に悩まされました。

それから私はを使用するというアイデアを思いつきましたObject.entries({...})、それはであるArrayを返しますIterable

また、アクセル・ラウシュマイヤー博士はこれについて優れた回答を投稿しました。プレーンオブジェクトが反復できない理由を参照してください


0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.