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 지원 ) 에서 코드를 실행할 때 예상되는 순서대로 데이터를 올바르게 기록합니다 .

해키의 출력 ...

기본적으로 {}객체는 반복 할 수 없지만 그 이유는 무엇입니까? 반복 가능한 객체의 잠재적 인 이점보다 단점이 더 클까요? 이것과 관련된 문제는 무엇입니까?

때문에 또한, {}객체는 "어레이 같은"컬렉션과는 다른 예 : "반복자는 객체" 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

1
Symbol.iterator속성이있는 모든 것은 반복 가능합니다. 따라서 그 속성을 구현하면됩니다. 객체가 반복 불가능한 이유에 대한 한 가지 가능한 설명은 모든 것이 객체이기 때문에 모든 것이 반복 가능 하다는 것을 의미 할 수 있습니다 (물론 기본 요소 제외). 그러나 함수 또는 정규식 객체를 반복한다는 것은 무엇을 의미합니까?
Felix Kling 2015

7
여기서 실제 질문은 무엇입니까? ECMA가 결정을 내린 이유는 무엇입니까?
Steve Bennett

3
객체는 속성의 순서를 보장하지 않기 때문에 예측 가능한 순서를 기대할 수있는 반복 가능한 정의에서 벗어나는지 궁금합니다.
jfriend00 2015-04-27

2
"왜", 당신은에 문의해야합니다에 대한 신뢰할 수있는 답변을 얻을하려면 esdiscuss.org
펠릭스 클링을

1
@FelixKling-ES6에 대한 게시물입니까? "다음 버전의 ECMAScript"는 시간이지나면서 잘 작동하지 않기 때문에 어떤 버전에 대해 말하고 있는지 편집해야합니다.
jfriend00 2015

답변:


42

나는 이것을 시도 할 것이다. 나는 ECMA와 관련이 없으며 그들의 의사 결정 과정에 대한 가시성이 없기 때문에 그들이 아무것도하지 않았거나하지 않은 이유 를 명확히 말할 수 없습니다 . 그러나 나는 내 가정을 진술하고 최선을 다할 것입니다.

1. for...of처음에 구성을 추가하는 이유는 무엇 입니까?

JavaScript에는 for...in객체의 속성을 반복하는 데 사용할 수 있는 구조가 이미 포함되어 있습니다. 그러나 객체의 모든 속성을 열거하고 간단한 경우에만 예측 가능하게 작동하는 경향이 있으므로 실제로 forEach 루프 는 아닙니다.

그것은 (그것의 사용이 하나되는 경향이 배열에 포함하여 더 복잡한 경우에 고장 낙담하거나 철저하게 난독 사용에 필요한 안전 장치에 의해 for...in배열을 올바르게 ). hasOwnProperty(다른 것들 중에서) 사용하여 해결할 수 있지만 약간 투박하고 우아하지 않습니다.

따라서 내 가정은 for...of구성과 관련된 결함을 해결하고 for...in반복 할 때 더 큰 유용성과 유연성을 제공하기 위해 구성 이 추가되고 있다는 것입니다. 사람들은 치료하는 경향이 for...inA와forEach 일반적으로 모든 컬렉션에 적용 할 수 루프 가능한 모든 컨텍스트에서 정상적인 결과를 생성 있지만, 그렇게되는 것은 아닙니다. for...of루프 수정있다.

또한 기존 ES5 코드가 ES6에서 실행되고 ES5에서와 동일한 결과를 생성하는 것이 중요하다고 가정합니다. 따라서 예를 들어 for...in구성 의 동작에 대한 주요 변경 사항을 만들 수 없습니다 .

2. 어떻게 for...of작동합니까?

참조 문서는 이 부분에 유용합니다. 특히 속성을 iterable정의하는 경우 개체가 고려 됩니다 Symbol.iterator.

속성 정의는 컬렉션의 항목을 one, by, one으로 반환하고 가져올 항목이 더 있는지 여부를 나타내는 플래그를 설정하는 함수 여야합니다. 일부 객체 유형에 대해 미리 정의 된 구현이 제공되며 for...of반복기 함수에 대한 단순히 대리자를 사용하는 것이 비교적 명확 합니다.

이 접근 방식은 자체 반복기를 제공하는 것이 매우 간단하므로 유용합니다. 이전에는 없었던 속성을 정의하는 데 의존하기 때문에 접근 방식이 실질적인 문제를 제시 할 수 있었을 수 있습니다. for...in루프에는 키 등으로 표시되지 않습니다 .) 그래서 그것은 사실이 아닙니다.

실용적이지 않은 문제는 제쳐두고 모든 객체를 미리 정의 된 새 속성으로 시작하거나 "모든 객체가 컬렉션"이라고 암시 적으로 말하는 것은 개념적으로 논란의 여지가있는 것으로 간주되었을 수 있습니다.

3. 개체 가 기본적으로 iterable사용 되지 않는 이유는 무엇 for...of입니까?

생각 엔 이것이 다음의 조합이라는 것입니다.

  1. iterable기본적으로 모든 개체 를 만드는 것은 이전에 속성이 없었던 속성을 추가하거나 개체가 (필연적으로) 컬렉션이 아니기 때문에 허용되지 않는 것으로 간주되었을 수 있습니다. Felix가 언급했듯이 "함수 또는 정규식 객체를 반복한다는 것은 무엇을 의미합니까?"
  2. 간단한 객체는 이미를 사용하여 반복 할 수 for...in있으며 기본 제공 반복기 구현이 기존 for...in동작 과 다르게 / 더 잘 수행 할 수있는 작업이 명확하지 않습니다 . 따라서 # 1이 잘못되고 속성 추가가 허용 되더라도 유용 하지 않을 수 있습니다. .
  3. 자신의 물건을 만들고 싶은 사용자 iterableSymbol.iterator속성 을 정의하여 쉽게 수 있습니다 .
  4. ES6 사양은 기본적으로 일반 객체를 사용하는 것보다 몇 가지 작은 이점 있는 Map 유형 도 제공합니다 . iterableMap .

참조 문서에서 # 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다음과 같은 작업을 수행하여 원하는 방식으로 개체 를 만들 수도 있습니다 (또는 대신 할당하여 모든 개체 iterable를 만들 수 있습니다 Object.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
    };
};

여기에서 (크롬에서) 재생할 수 있습니다 : http://jsfiddle.net/rncr3ppz/5/

편집하다

업데이트 된 질문에 대한 응답으로 예, ES6 iterable스프레드 연산자 를 사용하여을 배열 로 변환 할 수 있습니다.

그러나 이것은 아직 Chrome에서 작동하지 않는 것 같거나 적어도 내 jsFiddle에서 작동하도록 할 수 없습니다. 이론적으로는 다음과 같이 간단해야합니다.

var array = [...myIterable];

obj[Symbol.iterator] = obj[Symbol.enumerate]마지막 예에서 왜하지 않습니까?
Bergi

@Bergi-문서에서 그것을 보지 못했기 때문에 (그리고 여기에 설명 된 그 속성을 보지 못하고 있습니다 ). 반복자를 명시 적으로 정의하는 데 찬성하는 한 가지 주장은 필요한 경우 특정 반복 순서를 쉽게 적용 할 수 있다는 것입니다. 반복 순서가 중요하지 않고 (또는 기본 순서가 괜찮은 경우) 한 줄 바로 가기가 작동하는 경우 더 간결한 접근 방식을 사용하지 않을 이유가 거의 없습니다.
aroth

죄송합니다 [[enumerate]]. 잘 알려진 기호 (@@ enumerate)는 아니지만 내부 방법입니다. 나는해야 할 것입니다obj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
Bergi

토론의 실제 프로세스가 잘 문서화되어있는 경우 이러한 모든 추측은 어떻게 사용됩니까? "그러므로 내 가정은 for ... in 구문과 관련된 결함을 해결하기 위해 for ... of 구문이 추가된다는 것입니다."라고 말하는 것은 매우 이상합니다. 아니요. 모든 항목을 반복하는 일반적인 방법을 지원하기 위해 추가되었으며 반복 가능 자체, 생성기, 맵 및 세트를 포함한 광범위한 새 기능 세트의 일부입니다. 객체 for...in속성 을 반복하는 목적이 다른를 대체하거나 업그레이드하는 것은 아닙니다 .

2
모든 개체가 컬렉션이 아니라는 점을 다시 한 번 강조합니다. 객체는 매우 편리하기 때문에 오랫동안 그렇게 사용되어 왔지만 궁극적으로 실제로는 컬렉션이 아닙니다. 그것이 우리가 Map지금 가지고있는 것 입니다.
Felix Kling 2015

9

Objects는 매우 좋은 이유로 자바 스크립트에서 반복 프로토콜을 구현하지 않습니다. JavaScript에서 개체 속성을 반복 할 수있는 두 가지 수준이 있습니다.

  • 프로그램 수준
  • 데이터 수준

프로그램 수준 반복

프로그램 수준에서 개체를 반복 할 때 프로그램 구조의 일부를 검사합니다. 반사 작업입니다. 일반적으로 데이터 수준에서 반복되는 배열 유형으로이 문을 설명해 보겠습니다.

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배열의 길이를 나타내므로 프로그램 도메인에 속합니다.

이는 단순히 유형을 확인하는 것만으로는 프로그램과 데이터 수준을 구분할 수 없음을 의미합니다.


평범한 오래된 자바 스크립트 객체에 대한 반복 프로토콜의 구현은 데이터 수준에 의존한다는 것을 이해하는 것이 중요합니다. 그러나 방금 살펴본 것처럼 데이터와 프로그램 수준 반복 사이의 확실한 구분은 불가능합니다.

Arrays를 사용하면 이러한 구분은 간단합니다. 정수형 키를 가진 모든 요소는 데이터 요소입니다. Object에 동등한 기능이 있습니다 : enumerable설명자. 그러나 이것에 의존하는 것이 정말로 바람직합니까? 나는 그렇지 않다고 믿습니다! enumerable설명 자의 의미 가 너무 모호합니다.

결론

모든 개체가 컬렉션이 아니기 때문에 개체에 대한 반복 프로토콜을 구현하는 의미있는 방법은 없습니다.

개체 속성이 기본적으로 반복 가능한 경우 프로그램과 데이터 수준이 혼합되었습니다. 자바 스크립트의 모든 복합 유형이 일반 개체를 기반으로하기 때문에이 적용 것입니다 ArrayMap뿐만 아니라.

for..in, Object.keys, Reflect.ownKeys등 모두 반사 및 데이터 반복 사용할 수 있습니다, 명확한 구분이 정기적으로 할 수 없습니다. 주의하지 않으면 메타 프로그래밍과 이상한 종속성으로 빠르게 끝납니다. Map추상 데이터 유형을 효과적으로 프로그램 및 데이터 수준의 가미하여 종료됩니다. 훨씬 더 흥미 진진 Map하더라도 ES2015에서 가장 중요한 성과 라고 생각 합니다 Promise.


3
+1, "모든 개체가 컬렉션이 아니기 때문에 개체에 대한 반복 프로토콜을 구현하는 의미있는 방법은 없습니다." 요약하다.
Charlie Schliesser

1
나는 그것이 좋은 주장이라고 생각하지 않는다. 개체가 컬렉션이 아닌 경우 왜 반복하려고합니까? 모든 객체가 컬렉션 이 아니라는 것은 중요 하지 않습니다. 그렇지 않은 객체를 반복하려고 시도하지 않기 때문입니다.
BT

실제로 모든 개체 컬렉션이며 컬렉션이 일관 적인지 여부를 결정하는 것은 언어에 달려 있지 않습니다. 배열과 맵은 관련없는 값도 수집 할 수 있습니다. 요점은 사용에 관계없이 모든 객체의 키를 반복 할 수 있으므로 해당 값을 반복하지 않아도된다는 것입니다. 배열 (또는 다른 컬렉션) 값을 정적으로 형식화하는 언어에 대해 이야기하고 있다면 그러한 제한에 대해 이야기 할 수 있지만 JavaScript는 말할 수 없습니다.
Manngo

모든 개체가 컬렉션이 아니라는 주장은 의미가 없습니다. 반복자는 하나의 목적 (컬렉션 반복) 만 있다고 가정합니다. 개체의 기본 반복기는 해당 속성이 나타내는 것이 무엇이든 (컬렉션이든 다른 것이 든) 개체 속성의 반복자가됩니다. Manngo가 말했듯이, 객체가 컬렉션을 나타내지 않는다면, 그것을 컬렉션처럼 취급하지 않는 것은 프로그래머의 몫입니다. 디버그 출력을 위해 객체의 속성을 반복하고 싶습니까? 컬렉션 이외에도 많은 이유가 있습니다.
jfriend00

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]의도하지 않은 결과를 확장 할 수있을뿐만 아니라 사용하는 예를보고 싶습니다 .
boombox

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

이것은 최신 접근 방식입니다 (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);
    }
}

2

나는 또한이 질문으로 귀찮았다.

그럼 내가 사용하는 아이디어를 내놓았다 Object.entries({...})그것이 반환 Array인을Iterable .

또한 Axel Rauschmayer 박사는 이에 대한 훌륭한 답변을 게시했습니다. 일반 객체가 반복 할 수없는 이유 참조


0

기술적으로 이것은 왜 그럴까요? 하지만 위의 Jack Slocum의 답변을 BT의 의견에 따라 Object를 반복 가능하게 만드는 데 사용할 수 있습니다.

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 수 있다면 간단한 주장을 볼 수 없습니다.

한 가지 가능한 제안은 iterable이 객체 유형 이므로 다른 객체가 시도에서 폭발 할 경우를 대비하여 객체의 하위 집합으로 iterable이 제한되었을 수 있다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.