for..in
The for..in
iterate on the enumerable properties of an object, as long as the keys are strings (because the keys can also be Symbol
's).
Note that this is true for any object, not just arrays. Ex:
function forIn(obj) {
for (let i in obj) {
console.log(`${i} = ${obj[i]}`);
}
}
// testando com array
let x = [0, 1, 2];
x[Symbol('simbolo')] = 'symbol'; // chave não é string, não é mostrada no for..in
Object.defineProperty(x, 'no', { value: 10, enumerable: false }); // não-enumerável, não é mostrada no for..in
Object.defineProperty(x, 'yes', { value: 10, enumerable: true }); // enumerável, é mostrada no for..in
// definindo propriedades nos protótipos, todos os arrays a terão
Array.prototype.bla = 'bla';
Object.prototype.all = 'all';
console.log('iterando no array');
forIn(x); // mostra 0, 1, 2, "yes", "bla" e "all"
console.log(Object.entries(x)); // mostra 0, 1, 2 e "yes" (não mostra o "bla" nem o "all")
// testando com um objeto qualquer
let obj = {
a: 1,
[Symbol('c')]: 3 // símbolo, não é mostrada no for..in
};
Object.defineProperty(obj, 'b', { value: 2, enumerable: false }); // não-enumerável, não é mostrada no for..in
console.log('iterando no objeto');
forIn(obj); // mostra "a" e "all"
console.log(Object.entries(obj)); // mostra somente "a"
// testando com string
let s = 'a';
console.log('iterando na string');
forIn(s); // mostra o "all" (setado no prototype de Object) e 3 "caracteres"
console.log(Object.entries(s)); // não mostra o "all"
To another answer said to do for (let i in x)
would be equivalent to traversing the elements of Object.entries(x)
, but there is a difference: Object.entries
does not carry the properties set in the prototype of the object (such as the properties "bla" and "all" of the above example, which were defined respectively in Array.prototype
and Object.prototype
). Already the for..in
brings these properties, so if you wanted to ignore them, you’d have to do something like:
for (let i in obj) {
if (obj.hasOwnProperty(i)) console.log(`${i} = ${obj[i]}`);
}
Then yes the "bla" and "all" would be ignored.
In the case of the string, it is even more curious. Although it only has 2 "characters" (the letter "a" and emoji), 4 properties are returned: the "all" (inherited from Object.prototype
) and the 3 indexes that match your "characters". In fact the emoji was broken in 2 parts, as explained here.
Another important detail, in the case of arrays, is that the documentation says that it is not guaranteed that the elements are returned in the order of their numerical indices (There is no Guarantee that for...in
will Return the Indexes in any particular order). Testing on Chrome, I saw that it iterates in order, but this may not occur in all implementations.
Another case where gives difference to a for
traditional is when we do something like this:
let x = [];
x[5] = 5;
// imprime só o 5
for(let i in x) console.log(`${i} = ${x[i]}`);
// imprime 5 vezes "undefined" antes do 5
for(let i = 0; i < x.length; i++) console.log(`${i} = ${x[i]}`);
Anyway, an important point is that the for..in
works with any object, iterating by the enumerable properties of it.
for..of
The for..of
, unlike the for..in
, does not work with any object, but only with those who are everlasting. This includes strings, arrays, and objects array-like (such as the NodeList
returned by querySelectorAll
). To documentation also explains how to create your own kind.
Ex:
function forOf(obj) {
for (let i of obj) {
console.log(i);
}
}
// testando com array
let x = [0, 1, 2];
x[Symbol('simbolo')] = 'symbol';
Object.defineProperty(x, 'no', { value: 10, enumerable: false });
Object.defineProperty(x, 'yes', { value: 10, enumerable: true });
// definindo propriedades nos protótipos, todos os arrays a terão
Array.prototype.bla = 'bla';
Object.prototype.all = 'all';
forOf(x); // mostra 0, 1, 2
// com uma string
let s = 'a';
forOf(s); // mostra o "a" e o emoji
// testando com um objeto qualquer
let obj = {
a: 1,
[Symbol('c')]: 3
};
Object.defineProperty(obj, 'b', { value: 2, enumerable: false });
forOf(obj); // TypeError: obj is not iterable
Note that now the "extra" properties, whether enumerable or not, defined in the object itself or in the prototype, are all ignored.
In the case of the array, only the elements are shown because the iterator of an array has been set to work like this: starts from zero and increases until you reach length
array - so the case below gives difference to a for..in
:
let x = [];
x[3] = 3;
x.bla = 'bla';
x.length = 5;
// imprime só o 3 e "bla"
console.log('for..in só pega as propriedades que existem');
for(let i in x) console.log(x[i]);
// imprime 3 vezes "undefined" antes do 3, depois outro "undefined"
console.log('for..of itera por todos os índices, até o length');
for(let i of x) console.log(i);
Note the case above: for..in
only perpetuated by existing properties (in this case "3" and "bla"). Already for..of
by numerical indexes, starting from zero and going up to length - 1
(that is, tried to access the indexes 0, 1 and 2, which as there are no, result in undefined
, then accessed index 3, and then tried to access index 4 - since the length
is 5, then the loop goes to index 4 - which as it does not exist, also results in undefined
).
In the case of the string, the emoji is no longer "broken" and is displayed correctly, because the iterator of String
ensures this behavior (it iterates over the code points of the same - read the question already indicated above to better understand).
And in the case of the object, it gives error because it is not eternal (Typeerror: obj is not iterable). In order for it to work with it, I would have to define an iterator for it. Something like this:
let obj = {
a: 10, b: 20
};
// iterador: retorna os valores das propriedades deste objeto
obj[Symbol.iterator] = function* () {
for (let p in this) yield this[p];
};
for (let i of obj) console.log(i); // imprime 10 e 20
In case, I used for..in
internally to iterate through the properties, and made the iterator return the respective property value (but I could have used any other internal logic to return the values I wanted, even returning values that are not necessarily related to the properties of the object). This way it is possible to iterate on this object with for..of
.
In the case of native objects that can be iterated with for..of
(as arrays and strings), they internally already implement their own iterator. For others, just follow the documentation, which gives instructions to make your objects conform to the iteration protocols.
forEach
Unlike for..in
and for..of
, that are constructions of language, forEach
is a method of Array
(although it is not exclusive to this, because several other types also have a similar method, as Set
, NodeList
, etc.).
One thing they all have in common is that you must pass a function of callback that is called for each of the elements being traversed. For example, in the case of arrays, it iterates for the numeric indices of the same, and in the callback you pass the element and its index:
let x = ['a', 'b', 'c'];
x.forEach(function (elemento, indice) { // imprime os índices e os respectivos elementos
console.log(`x[${indice}] = ${elemento}`);
});
The way it works varies according to type. In the case of arrays, it traverses the numerical indices until it reaches the length
of the same, but unlike for..of
, the forEach
check if the index exists (see the algorithm in the specification). So we can have situations like this:
let x = [];
x[2] = 2;
x.forEach(function (elemento, indice) { // imprime somente o 2 (os índices 0 e 1 não existem)
console.log(`x[${indice}] = ${elemento}`);
});
x[5] = 5;
x.forEach(function (elemento, indice) { // imprime somente o 2 e o 5 (os outros índices não existem)
console.log(`x[${indice}] = ${elemento}`);
});
Already other types can implement the forEach
differently. For example, a Set
does not define keys, so their values end up being the "keys" themselves. In a Map
the forEach
also iterates for non-numeric keys, etc. Each type implements its own forEach
according to what makes sense for its purpose.
Felipe, is there anything missing in the answers below? From what I understand, they answer what was asked - unless I got it wrong, of course :-)
– hkotsubo