Używanie obiektów w pętlach For Of

84

Dlaczego nie można używać obiektów w pętlach for of? A może to błąd przeglądarki? Ten kod nie działa w Chrome 42, mówiąc, że undefined nie jest funkcją:

test = { first: "one"}

for(var item of test) {
  console.log(item)
}
Daniel Herr
źródło
Czy test jest tablicą lub obiektem?
Kick Buttowski
9
@KickButtowski, nie widzisz? To zdecydowanie przedmiot.
Green
4
for (let key of Object.keys (test)) {...}
zegarmistrz

Odpowiedzi:

69

For..of pętla obsługuje tylko iterowalny obiektów, takich jak macierze, nie sprzeciwia.

Aby iterować po wartościach obiektu, użyj:

for (var key in test) {
    var item = test[key];
}
Overv
źródło
3
@DanielHerr Posiadanie .iterablefunkcji członkowskiej, która jest przyczyną błędu, gdy próbujesz użyć jej na obiekcie (który jej nie ma). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Overv
4
Mam na myśli, dlaczego przedmioty tego nie mają? Jaki byłby problem z natywnym dodaniem go?
Daniel Herr
3
@DanielHerr Nie mam odpowiedzi na to pytanie, będziesz musiał zapytać ludzi, którzy projektują język.
Overv
6
@DanielHerr Gdyby „klasa bazowa” obiektu była iterowalna, tak samo jak „podklasa” funkcji / daty / etc, pośród innych komplikacji. Odwiedź stronę esdiscuss.org/topic/es6-iteration-over-object-values#content-5, aby uzyskać dokładniejsze / dokładniejsze omówienie swojego pytania.
natevw
5
Dzięki temu rozwiązaniu for… w rozwiązaniu technicznym nie musisz jeszcze sprawdzać if (test.hasOwnProperty(key)){ ... }? Czy to nie jest potrzebne?
tenisista
40

Możesz użyć tej składni:

let myObject = {first: "one"};

for(let [key, value] of Object.entries(myObject)) {
    console.log(key, value); // "first", "one"
}

Jednak, Object.entries ma teraz słabe wsparcie nie działa w IE lub iOS Safari. Będzieszprawdopodobnie może wymagać wypełnienia.

mpen
źródło
33

Jeśli przechowujesz dane w magazynie klucza i wartości, użyj go,Map który jest wyraźnie przeznaczony do tego celu.

Jeśli jednak musisz użyć obiektu, ES2017 (ES8) pozwala na użycie Object.values:

const foo = { a: 'foo', z: 'bar', m: 'baz' };
for (let value of Object.values(foo)) {
    console.log(value);
}

Jeśli nie jest to jeszcze obsługiwane, użyj wypełnienia: alternatywna wersja dlaObject.values()

I na koniec, jeśli obsługujesz starsze środowisko, które nie obsługuje tej składni, będziesz musiał uciec się do używania forEachi Object.keys:

var obj = { a: 'foo', z: 'bar', m: 'baz' };
Object.keys(obj).forEach(function (prop) {
    var value = obj[prop];
    console.log(value);
});
Qantas 94 Heavy
źródło
nie można rozszerzyć prototypu Object, aby to obsługiwał?
Sonic Soul
1
@SonicSoul: technicznie tak, ale generalnie nie zaleca się rozszerzania prototypu Object, ponieważ (prawie) wszystko po nim dziedziczy.
Qantas 94 Heavy
1
Object.entriesmożna wypełnić polietylenem bez dotykania prototypu.
otwarte
5
Po co używać map zamiast obiektów?
Daniel Herr
1
Czy jest jakaś przewaga w stosowaniu tych złożonych przykładów nad prostymi for-in?
1252748
18

Iterator, Iterable i for..of w ECMAScript 2015 / ES6

let tempArray = [1,2,3,4,5];

for(element of tempArray) {
  console.log(element);
}

// 1
// 2
// 3
// 4
// 5

Ale jeśli to zrobimy

let tempObj = {a:1, b:2, c:3};

for(element of tempObj) {
   console.log(element);
}
// error

Otrzymujemy błąd, ponieważ pętla for..of działa tylko na Iterables , czyli na obiekcie, który ma iterator @@, który jest zgodny z protokołem Iterator , co oznacza, że ​​musi mieć obiekt z następną metodą. Następna metoda nie przyjmuje argumentów i powinna zwrócić obiekt z tymi dwiema właściwościami.

gotowe : sygnalizuje zakończenie sekwencji, gdy prawda, a fałsz oznacza, że ​​może być więcej wartości wartość : to jest bieżąca pozycja w sekwencji

Tak więc, aby obiekt był iterowalny , to znaczy, że będzie działał z z , możemy:

1 .Dokonać obiekt iterowalny poprzez przypisanie To mistyczne @@ iterator nieruchomość przez Symbol.iterator property.Here sposób:

let tempObj = {a:1, b:2, c:3};

tempObj[Symbol.iterator]= () => ({
next: function next () {
return {
    done: Object.keys(this).length === 0,
    value: Object.keys(this).shift()
     }
    }
  })

for(key in tempObj){
 console.log(key)
}
// a
// b
// c

2.Wykorzystanie Object.entries , który zwraca iterable :

let tempObj = {a:1, b:2, c:3};

for(let [key, value] of Object.entries(tempObj)) {
    console.log(key, value);
}
// a 1
// b 2
// c 3

3. Użyj Object.keys , oto jak:

let tempObj = {a:1, b:2, c:3};
for (let key of Object.keys(tempObj)) {
    console.log(key);
}

// a
// b
// c

Mam nadzieję że to pomoże!!!!!!

Manishz90
źródło
16

Zrobiłem iterowalne obiekty za pomocą tego kodu:

Object.prototype[Symbol.iterator] = function*() {
 for(let key of Object.keys(this)) {
  yield([ key, this[key] ])
} }

Stosowanie:

for(let [ key, value ] of {}) { }

Alternatywnie:

for(let [ key, value ] of Object.entries({})) { }
Daniel Herr
źródło
48
Nie mam pojęcia, dlaczego jest to akceptowane rozwiązanie. Modyfikowanie prototypu, chyba że jest to polyfill, jest zawsze okropnym pomysłem.
user1703761
2
@ user1703761 Jest to rozwiązanie zaakceptowane, ponieważ działa. Proszę wyjaśnić, jakie problemy spowoduje to, jeśli będzie tak straszne.
Daniel Herr,
9
Istnieje wiele różnych problemów, głównie ze zgodnością w przód. Jednym z przykładów jest to, że Array.prototype.includes, które wcześniej nazywało się, zawiera, ale Moo Tools rozszerzyło prototyp, a implementacja była niekompatybilna, zobacz bugzilla.mozilla.org/show_bug.cgi?id=1075059 Sprawdź także desaster biblioteki Prototype;)
user1703761
4
Uważam, że nie spowoduje to problemów ze zgodnością w przód, ponieważ jeśli iterator zostałby dodany do obiektów, nadpisałby go, a jeśli iterator zostałby dodany do podtypu obiektu, użyłby iteratora podtypu.
Daniel Herr,
4
Hej, modyfikowanie prototypu to zły pomysł !!! Wstydźmy się OP za udzielenie odpowiedzi na to pytanie!
NiCk Newman
12

Ponieważ literał obiektu nie ma właściwości Symbol.iterator . Mówiąc konkretnie, możesz iterować tylko po Stringach , Array , Map , Set , arguments , NodeList (nie jest szeroko obsługiwany) i Generatorze z pętlą for ... of .

Aby poradzić sobie z iteracją dosłowną obiektu, masz dwie opcje.

dla w

for(let key in obj){
    console.log(obj[key]); 
}

Object.keys + forEach

Object.keys(obj).forEach(function(key){
    console.log(obj[key]);
});
Chwytak
źródło
3

Odpowiedź brzmi: Nie. Nie można używać For..Of z literałami Object.

Zgadzam się z Overv, że For..Of jest tylko dla iterable. Miałem dokładnie to samo pytanie, ponieważ używam Objects do iteracji po kluczach i wartościach z for..in. Ale właśnie zdałem sobie sprawę, że do tego służą MAPY i ZESTAWY ES6 .

let test = new Map();
test.set('first', "one");
test.set('second', "two");

for(var item of test) {
  console.log(item); // "one" "two"
}

W związku z tym, że osiąga się cel nie trzeba wykorzystywać for..in (stwierdzenia prawidłowości z hasOwnProperty ) i bez konieczności korzystania Object.keys ().

Ponadto twoje klucze nie są ograniczone do ciągów. Możesz używać liczb, obiektów lub innych literałów.

cuadraman
źródło
2

Literały obiektów nie mają wbudowanych iteratorów, które są wymagane do pracy z for...ofpętlami. Jeśli jednak nie chcesz kłopotać się dodawaniem własnego [Symbol.iterator]obiektu do obiektu, możesz po prostu użyć tej Object.keys()metody. Ta metoda zwraca Arrayobiekt, który ma już wbudowany iterator, więc możesz go użyć z for...ofpętlą taką jak ta:

const myObject = {
    country: "Canada",
    province: "Quebec",
    city: "Montreal"
}

for (let i of Object.keys(myObject)) {
    console.log("Key:", i, "| Value:", myObject[i]);
}

//Key: country | Value: Canada
//Key: province | Value: Quebec
//Key: city | Value: Montreal
Chunky Chunk
źródło
Używanie kluczy za każdym razem jest większym problemem niż jednorazowe dodanie iteratora. Ponadto Object.keys () to ES5.
Daniel Herr
1

Możliwe jest zdefiniowanie iteratora dla dowolnego obiektu podającego, w ten sposób można umieścić inną logikę dla każdego obiektu

var x = { a: 1, b: 2, c: 3 }
x[Symbol.iterator] = function* (){
    yield 1;
    yield 'foo';
    yield 'last'
}

Następnie po prostu wykonaj iterację bezpośrednio x

for (let i in x){
    console.log(i);
}
//1
//foo
//last

Można zrobić to samo na Object.prototypeobiekcie i mieć ogólny iterator dla wszystkich obiektów

Object.prototype[Symbol.iterator] = function*() {
    for(let key of Object.keys(this)) {
         yield key 
    } 
 }

następnie iteruj swój obiekt w ten sposób

var t = {a :'foo', b : 'bar'}
for(let i of t){
    console.log(t[i]);
}

Albo w ten sposób

var it = t[Symbol.iterator](), p;
while(p = it.next().value){
    console.log(t[p])
}
Yaki Klein
źródło
1

Wykonałem tylko następujące czynności, aby łatwo pocieszyć moje rzeczy.

for (let key in obj) {
  if(obj.hasOwnProperty(key){
    console.log(`${key}: ${obj[key]}`);
  }
}
DaFrenzy
źródło
0

A co z używaniem

function* entries(obj) {
    for (let key of Object.keys(obj)) {
        yield [key, obj[key]];
    }
}

for ([key, value] of entries({a: "1", b: "2"})) {
    console.log(key + " " + value);
}
user1703761
źródło
0

w ES6 możesz iść z generatorem:

var obj = {1: 'a', 2: 'b'};

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

let generator = entries(obj);

let step1 = generator.next();
let step2 = generator.next();
let step3 = generator.next();

console.log(JSON.stringify(step1)); // {"value":["1","a"],"done":false}
console.log(JSON.stringify(step2)); // {"value":["2","b"],"done":false}
console.log(JSON.stringify(step3)); // {"done":true}

Oto plik jsfiddle. W wyniku otrzymasz obiekt z klawiszami "value"i "done". "Value"zawiera wszystko, co chcesz , aby miał i "done"jest bieżącym stanem iteracji w bool.

Serge Nikolaev
źródło
0

Używając niszczenia tablicy, możesz iterować w następujący sposób, używając forEach

const obj = { a: 5, b: 7, c: 9 };

Object.entries(obj).forEach(([key, value]) => {
  console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});
Nur Rony
źródło