How do "for in, for of, foreach" loops traverse the array?

Asked

Viewed 337 times

8

I’ve always used the loop for in which it differentiates a little from the mentioned loops, but the question is how these loops travel the array as, for example, the loop for usually follows these steps to traverse the array:

for (let i = 0; i <= 10; i ++) {
    console.log(i);
}

  1. Start the variable i with the value 0.
  2. The value of the variable i is less than or equal to 10?
    • If it is true execute the code block.
      • At the end increment 1 in the variable i.
      • Go back to step 2.
    • If it is false, stop the loop and follow the normal code execution flow.

Now, have we loops mentioned in the question title how they would work?

  • 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 :-)

2 answers

8

The noose for is a project pattern to repeat commands with a startup, a step that will always run at each iteration and a condition that will determine when to stop repeating.

Some languages have created syntax to allow new design patterns that do more common things, so some situations can be demonstrated more simply and avoid certain errors, and eventually even managing to do some optimization (in others there may be an extra cost).

Javascript does not have a command foreach, just a function with that name and it’s just a function that has a bond inside, generally it doesn’t make sense to iusá it, but people love it, it’s just cost without advantage, on the contrary have disadvantages.

Exists in language only the for ... in and the for ... of. Differences that have already been explained in What is the difference between for...of and for.. in?.

So these bonds already take an object and sweep away all its members. The language initializes the variable to start the first element, the condition will determine when the elements are finished and the step is what makes you go to the next element. Depending on the type of object the language can do this in a different way to suit. It does no different than what you should do in the same situation, if you don’t do it the same it may be that you’re doing it wrong.

So

var objeto = { a: 'A', b: 'B', c: 'C' };
for (var i in objeto) console.log(objeto[i]);

In fact it would be more or less like this:

var objeto = { a: "A", b: "B", c: "C" };
var obj = Object.entries(objeto);
const it = obj.values();
let entry;
while (!(entry = it.next()).done) console.log(entry.value[1]);

var objeto = [1, 2, 3];
for (var i of objeto) console.log(i);

Turns more or less into:

var objeto = [1, 2, 3];
const it = objeto.values();
let entry;
while (!(entry = it.next()).done) console.log(entry.value);

May be more simplified (depends on implementation):

var objeto = [1, 2, 3];
let i = 0;
while (i < objeto.length) console.log(objeto[i++]);

I put in the Github for future reference.

Roughly speaking this and reinforcement that the exact way of functioning depends on implementation, as long as it follows the specification.

  • The way while (ì < objecto.length) only works array.

7

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.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.