Interview question: Why do we need forEach when we have a for loop?



There are so many loops in js, for for...in for...of forEach, some loops feel similar. Today we will discuss the difference between for loop and forEach. We discuss from several dimensions:

  1. The essential difference between for loop and forEach.
  2. The syntax difference between for loop and forEach.
  3. The performance difference between for loop and forEach.

essential difference

The for loop is a loop method that has been around since js was introduced. forEach is a method proposed by ES5 and mounted on the prototype of an iterable object, such as Array Set Map. forEach is an iterator, responsible for traversing iterable objects. So what are traversal, iteration, and iterable objects ?

Traversal : refers to the regular and one-time access to each member of the data structure.

Iteration : Iteration is a special form of recursion and a method provided by iterators. By default, data structure members are accessed one by one in a certain order. Iteration is also a traversal behavior.

Iterable objects : The iterable type was introduced in ES6. Array Set Map String arguments NodeList all belong to iterable. Their characteristic is that they all have the [Symbol.iterator] method, and the object containing it is considered an iterable iterable.
Insert image description here

After understanding this, you will know that forEach is actually an iterator. The essential difference between it and the for loop is that forEach is responsible for traversing (Array Set Map) iterable objects, while the for loop is a loop mechanism that can only be traversed through it. Out the array.

Let’s talk about what exactly an iterator is. Remember the Generator generator mentioned before. When it is called, it will generate an Iterator Object. It has a .next() method, which returns an Object {value:value,done:Boolean}, value returns the return value after yield. When yield ends, done becomes true, and the internal value is accessed through continuous calls and sequential iterations.

Iterator is a special kind of object. Its symbol in the ES6 specification is the next() method of the returned object, and the iteration behavior is judged in done. Iterators implement traversal without exposing the internal representation. Look at the code

let arr = [1, 2, 3, 4]  // 可迭代对象
let iterator = arr[Symbol.iterator]()  // 调用 Symbol.iterator 后生成了迭代器对象
console.log(iterator.next()); // {value: 1, done: false}  访问迭代器对象的next方法
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

We see it. As long as it is an iterable object, calling the internal Symbol.iterator will provide an iterator, and access the interior according to the next method returned by the iterator. This is also the implementation principle of for...of.

let arr = [1, 2, 3, 4]
for (const item of arr) {
    
    
    console.log(item); // 1 2 3 4 
}

Call the next method to return the value of the object and save it in item until the value is undefined and jump out of the loop. All iterable objects are available for consumption by for...of. Let’s look at other iterable objects:

function num(params) {
    
    
    console.log(arguments); // Arguments(6) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    let iterator = arguments[Symbol.iterator]()
    console.log(iterator.next()); // {value: 1, done: false}
    console.log(iterator.next()); // {value: 2, done: false}
    console.log(iterator.next()); // {value: 3, done: false}
    console.log(iterator.next()); // {value: 4, done: false}
    console.log(iterator.next()); // {value: undefined, done: true}
}
num(1, 2, 3, 4)

let set = new Set('1234')
set.forEach(item => {
    
    
    console.log(item); // 1 2 3 4
})
let iterator = set[Symbol.iterator]()
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

So we can also intuitively see that the Symbol.iterator property in the iterable object can generate an iterator when it is called, and forEach also generates an iterator and passes the value of each element in the internal callback function.

The syntax difference between for loop and forEach

Now that we understand the essential differences, what are the grammatical differences between them during application?

  1. Parameters of forEach.
  2. Interruption of forEach.
  3. forEach deletes its own element, and the index cannot be reset.
  4. The for loop controls the starting point of the loop.

Parameters of forEach
Do we really understand the complete parameter passing content of forEach? It goes something like this:

arr.forEach((self,index,arr) =>{
    
    },this)

self: The currently traversed element of the array. By default, the array elements are obtained from left to right.
index: The index of the current element of the array, the index of the first element is 0, and so on.

arr: The currently traversed array.

this: This points to the callback function.

let arr = [1, 2, 3, 4];
let person = {
    
    
    name: '1111111111111'
};
arr.forEach(function (self, index, arr) {
    
    
    console.log(`当前元素为${
    
    self}索引为${
    
    index},属于数组${
    
    arr}`);
    console.log(this.name+='真帅');
}, person)

We can use arr to implement array deduplication:

let arr1 = [1, 2, 1, 3, 1];
let arr2 = [];
arr1.forEach(function (self, index, arr) {
    
    
    arr.indexOf(self) === index ? arr2.push(self) : null;
});
console.log(arr2);   // [1,2,3]

Insert image description here
Interruption of forEach

In js, there is break return continue to interrupt the function or jump out of the loop. We will use some interrupt behaviors in the for loop. It is good for optimizing array traversal and search. However, because forEach is an iterator, it can only be used in sequence. The traversal is completed in sequence, so the above interruption behavior is not supported.

let arr = [1, 2, 3, 4],
    i = 0,
    length = arr.length;
for (; i < length; i++) {
    
    
    console.log(arr[i]); //1,2
    if (arr[i] === 2) {
    
    
        break;
    };
};

arr.forEach((self,index) => {
    
    
    console.log(self);
    if (self === 2) {
    
    
        break; //报错
    };
});

arr.forEach((self,index) => {
    
    
    console.log(self);
    if (self === 2) {
    
    
        continue; //报错
    };
});

What if I must break out of the loop in forEach? In fact, there is a way, with the help of try/catch:

try {
    
    
    var arr = [1, 2, 3, 4];
    arr.forEach(function (item, index) {
    
    
        //跳出条件
        if (item === 3) {
    
    
            throw new Error("LoopTerminates");
        }
        //do something
        console.log(item);
    });
} catch (e) {
    
    
    if (e.message !== "LoopTerminates") throw e;
};

If a return is encountered, an error will not be reported, but it will not take effect.

let arr = [1, 2, 3, 4];

function find(array, num) {
    
       
    array.forEach((self, index) => {
    
           
        if (self === num) {
    
         
            return index;      
        };   
    });
};
let index = find(arr, 2);// undefined

forEach deletes its own element, and the index cannot be reset.

In forEach, we have no control over the value of index. It will only increase mindlessly until it is greater than the length of the array and break out of the loop. Therefore, it is impossible to delete itself and reset the index. Let’s look at a simple example:

let arr = [1,2,3,4]
arr.forEach((item, index) => {
    
      
    console.log(item); // 1 2 3 4  
    index++;
});

The index will not change as it is increased or decreased within the function body. In actual development, it is very common to traverse an array and delete an item at the same time. Please pay attention when using forEach to delete.

The for loop can control the starting point of the loop

As mentioned above, the starting point of the forEach loop can only be 0 without human intervention, but the for loop is different:

let arr = [1, 2, 3, 4],
    i = 1,
    length = arr.length;

for (; i < length; i++) {
    
    
    console.log(arr[i]) // 2 3 4
};

Then the previous array traversal and deletion operations can be written as

let arr = [1, 2, 1],
    i = 0,
    length = arr.length;

for (; i < length; i++) {
    
    
    // 删除数组中所有的1
    if (arr[i] === 1) {
    
    
        arr.splice(i, 1);
        //重置i,否则i会跳一位
        i--;
    };
};
console.log(arr); // [2]
//等价于
var arr1 = arr.filter(index => index !== 1);
console.log(arr1) // [2]

The performance difference between for loop and forEach

In terms of performance comparison, we add a map iterator, which generates a new array just like filter. Let's compare what the performance of forEach map looks like in a browser environment:

Performance comparison : for > forEach > map in chrome 62 and Node.js v9.1.0 environment: for loop is 1 times faster than forEach, and forEach is about 20% faster than map.

Reason analysis for: The for loop has no additional function call stack and context, so its implementation is the simplest. forEach: ForEach, its function signature contains parameters and context, so the performance will be lower than for loop. map: The slowest reason for map is that map will return a new array. The creation and assignment of the array will result in the allocation of memory space, which will cause a large performance overhead.

If you nest the map in a loop, it will cause more unnecessary memory consumption. When you use an iterator to traverse an array, using map if you don't need to return a new array goes against the original design intention. When I was working on front-end development, I saw many people using map just to traverse arrays:

let data = [];
let data2 = [1,2,3];
data2.map(item=>data.push(item));

Guess you like

Origin blog.csdn.net/weixin_43228814/article/details/132922320