1
头图
Hello everyone, I’m Lin Yiyi. This is an article comparing the principles and performance of the three types of loops in JS. I hope I can bring you some help 😁

Performance comparison

Performance comparison of for loop and while loop

let arr = new Array(999999).fill(1)

console.time('forTime')
for(let i = 0; i< arr.length; i++){}
console.timeEnd('forTime')

console.time('whileTime')
let i = 0
while(i< arr.length){
    i ++ 
}
console.timeEnd('whileTime')
/* 输出
* forTime: 4.864990234375 ms
* whileTime: 8.35107421875 ms
*/
  • Using let statement, due to the for of the block-level scope in 060d19190829ca, the memory is released, and the running speed will be faster.
  • When using the var statement, because for while does not have the influence of block-level scope, the speed of the two is basically the same.

forEach(callback, thisArg) loop array

callback function will be executed once in each cycle, and it can also receive three parameters (currentValue, index, array) , index, array is also optional, and thisArg (optional) is the this point of the callback function.
  • Iterate over enumerable properties

    let arr = new Array(999999).fill(1)
    console.time('forEachTime')
    arr.forEach(item =>{} )
    console.timeEnd('forEachTime')
    // forEachTime: 25.3291015625 ms
  • Functional programming of forEach consumes more performance.

Thinking: Can using return in forEach interrupt the loop?

[1,2,4,5].forEach((item, index) => {
    console.log(item, index)
    return
})
// 1 0
// 2 1
// 4 2
// 5 3
It can be seen from the above that the use of return in forEach cannot break out of the loop.
So how to interrupt the forEach loop ,
  • You can use try catch
  • Or use other loops instead, such as replacing forEach with every and some. If the internal returns false in every, it jumps out, and it jumps out when the inside is true in some.

Simulate forEach

Array.prototype.myForEach = function (callback, context) {
    let i = 0,
        than = this,
        len = this.length;
    context = context ? window : context;
    for (; i < len; i++) {
        typeof callback === 'function' ? callback.call(context, than[i], i, than) : null
    }
}

let arr = [0, 1, 5, 9]
arr.myForEach((item, index, arr) => {
    console.log(item, index, arr)
})

//0 0 (4) [0, 1, 5, 9]
// 1 1 (4) [0, 1, 5, 9]
The results are accurate. Regarding the use of this point or call, you can see JS this points to and The analog implementation of call, apply, bind

for in loop

The cycle performance of for in The reason for the poor performance is because: for in will iterate all the attributes that can be enumerated on the object prototype chain.
let arr = new Array(999999).fill(1)
console.time('forInTime')
for(let key in arr){}
console.timeEnd('forInTime')
// forInTime: 323.08984375 ms
  • for in loop is mainly used for objects

    let obj = {
      name: '林一一',
      age: 18,
      0: 'number0',
      1: 'number1',
      [Symbol('a')]: 10
    }
    
    Object.prototype.fn = function(){}
    
    for(let key in obj){
    //    if(!obj.hasOwnProperty(key)) break 阻止获取原型链上的公有属性 fn
      console.log(key)
    }
    /* 输出
     0
     1
     name
     age
     fn
    */
  • (Disadvantages) for in loop mainly traverses numbers first, traversing from small to large
  • (Disadvantages) for in cannot traverse Symbol attributes (not enumerable).
  • (Disadvantages) for in will also traverse the enumerable properties in the public (prototype). You can use hasOwnProperty to prevent traversal of public attributes.

    Thinking

    1. How to get Symbol attribute

    Use Object.getOwnPropertySymbols() to get all Symbol attributes.
    let obj = {
      name: '林一一',
      age: 18,
      0: 'number0',
      1: 'number1',
      [Symbol('a')]:  10
    }
    
    Object.prototype.fn = function(){}
    
    let arr = Object.keys(obj).concat(Object.getOwnPropertySymbols(obj))
    console.log(arr)    //["0", "1", "name", "age", Symbol(a)]

for of loop

let arr = new Array(999999).fill(1)
console.time('forOfTime')
for(const value of arr){}
console.timeEnd('forOfTime')
// forOfTime: 33.513916015625 ms
The principle of the for of loop is that according to whether there is an iterator specification. All those with Symbol.iterator implement the iterator specification. For example, some of the arrays, Set,Map... and objects do not implement the Symbol.iterator specification, so they cannot be used. for of cycle.
  • Use the for of loop, first execute the function corresponding to the Symbol.iterator
  • The object contains a function next() which is executed once in a next() next() , and another object is returned in 060d19190831fe
  • This object contains two values done: represents whether the loop is over, true represents the end; value: represents the value returned each time.

    // Symbol.iterator 内部机制如下
    let arr = [12, 23, 34]
    arr[Symbol.iterator] = function () {
      let self = this,
          index = 0;
      return {
          next() {
              if(index > self.length-1){
                  return {
                      done: true,
                      value: undefined
                  }
              }
              return {
                  done: false,
                  value: self[index++]
              }
          }
      }
    }

    Thinking, how to make ordinary array-like can use for of loop

    Array-like is required to have and the result attribute name of the array-like test 0, 1, 2... , and must have the length attribute
    let obj = {
      0: 12,
      1: '林一一',
      2: 'age18',
      length: 3
    }
    // 
    obj[Symbol.iterator] = Array.prototype[Symbol.iterator]
    for (const value of obj) {
      console.log(value)   
    }
  • 12
  • Lin Yiyi
  • age18
    */

    > 只需要给类数组对象添加`Symbol.iterator`接口规范就可以了。

(Additional) Turn the argument set into a real array

arguments not an array?

  • arguments is an array of class (in fact, an object) discharging property is zero, 1, 2, ... Finally, there were the callee and length properties, arguments the __proto__ directly to the base class object , does not have an array of methods .

    Method one uses call(), [].slice/Array.prototype.slice()

    let array = [12, 23, 45, 65, 32]
    function fn(array){
      var args = [].slice.call(arguments)
      return args[0]
    }
    fn(array)   // [12, 23, 45, 65, 32]

    above slice combination call Why can change this can post arguments converted into an array? Let’s simulate handwriting and implement slice , and we will know the principle

    Array.prototype.mySlice = function(startIndex=0, endIndex){
      let array = this    // 通过 this 获取调用的数组
      let thisArray = []
      endIndex === undefined ? (endIndex = array.length) : null
      for(let i = startIndex; i< endIndex; i++){      // 通过 `length` 属性遍历
          thisArray.push(array[i])
      }
      return thisArray
    }
    
    // 测试一下没有问题
    let arr = [1, 3, 5, 6, 7, 23]
    let a 
    a = arr.mySlice()   // [1, 3, 5, 6, 7, 23]
    a = arr.mySlice(2, 6)   // [5, 6, 7, 23]
    By this gets called mySlice array, and then by length traversing the formation of a new array property returns. So change this to point to arguments and then arguments.length to return a new array to convert the class array into an array.

Come to think about the string can be converted into an array?

let a = [].slice.call('stringToArray')
console.log(a)  // ["s", "t", "r", "i", "n", "g", "T", "o", "A", "r", "r", "a", "y"]
The same is also possible, for the same reason as above. As for why the string (value type) is this , you can take a look at this article [Interview | call, apply, bind implementation principles and interview questions] ()

Method 2: Use ES6's spread operator ...

function fn(array){
    var args = [...arguments]
    return args
}
fn(12, 23, 45, 65, 32)   // [12, 23, 45, 65, 32]

Way three Array.from()

function fn(array){
    return Array.from(arguments)
}
fn(12, 23, 45, 65, 32)   // [12, 23, 45, 65, 32]

林一一
171 声望7 粉丝