大家好,我是林一一,这是一篇比拟 JS 中三类循环的原理和性能的文章,心愿能给你带来点帮忙

性能比拟

for 循环和 while 循环的性能比照

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 = 0while(i< arr.length){    i ++ }console.timeEnd('whileTime')/* 输入* forTime: 4.864990234375 ms* whileTime: 8.35107421875 ms*/
  • 应用 let 申明下的循环,因为 for 中块级作用域的影响,内存失去开释,运行的运行的速度会更快一些。
  • 应用 var 申明时因为for while 的循环都不存在块级作用域的影响,两者运行的速度基本一致。

forEach(callback, thisArg) 循环数组

callback 函数每一轮循环都会执行一次,且还能够接管三个参数(currentValue, index, array)index, array 也是可选的,thisArg(可选) 是回调函数的 this 指向。
  • 遍历可枚举的属性

    let arr = new Array(999999).fill(1)console.time('forEachTime')arr.forEach(item =>{} )console.timeEnd('forEachTime')// forEachTime: 25.3291015625 ms
  • 函数式编程的 forEach 性能耗费要更大一些。

思考:在 forEach 中应用 return 能中断循环吗?

[1,2,4,5].forEach((item, index) => {    console.log(item, index)    return})// 1 0// 2 1// 4 2// 5 3
从下面看出 forEach 中应用 return 是不能跳出循环的。
那么如何中断 forEach 的循环
  • 能够应用 try catch
  • 或应用其余循环来代替,比方 用 every 和some 代替 forEach,every 中外部返回 false是跳出,some 中外部是 true 时 跳出

模仿实现 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]
后果准确无误。对于 this 指向或 call 的应用的能够看看 JS this 指向 和 call, apply, bind的模仿实现

for in 循环

for in 的循环性能循环很差。性能差的起因是因为:for in 会迭代对象原型链上所有 能够枚举的属性。
let arr = new Array(999999).fill(1)console.time('forInTime')for(let key in arr){}console.timeEnd('forInTime')// forInTime: 323.08984375 ms
  • for in 循环次要用于对象

    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*/
  • (毛病) for in 循环次要遍历数字优先,由小到大遍历
  • (毛病) for in 无奈遍历 Symbol属性(不可枚举)。
  • (毛病) for in 会将私有(prototype) 中可枚举的属性也遍历了。能够应用 hasOwnProperty来阻止遍历私有属性。

    思考

    1. 怎么获取 Symbol 属性

    应用 Object.getOwnPropertySymbols(),获取所有 Symbol 属性。
    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 循环

let arr = new Array(999999).fill(1)console.time('forOfTime')for(const value of arr){}console.timeEnd('forOfTime')// forOfTime: 33.513916015625 ms
for of 循环的原理是依照是否有迭代器标准来循环的,所有带有 Symbol.iterator 的都是实现了迭代器标准,比方数组一部分类数组,Set,Map...对象没有实现 Symbol.iterator 标准,所以不能应用for of循环。
  • 应用 for of 循环,首先会先执行 Symbol.iterator 属性对应的函数且返回一个对象
  • 对象内蕴含一个函数 next() 循环一次执行一次 next()next() 中又返回一个对象
  • 这个对象内蕴含两个值别离是 done:代表循环是否完结,true 代表完结;value:代表每次返回的值

    // 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++]          }      }  }}

    思考,如何让一般的类数组能够应用 for of 循环

    类数组被需具备和数组类试的后果属性名从0, 1, 2...开始,且必须具备length 属性
    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
  • 林一一
  • age18
    */

    > 只须要给类数组对象增加`Symbol.iterator`接口标准就能够了。

(附加)将argument实参汇合变成真正的数组

arguments 为什么不是数组?

  • arguments 是类数组(其实是一个对象)属性从0开始排,顺次为0,1,2... 最初还有 callee和length 属性,arguments__proto__ 间接指向基类的 object,不具备数组的办法。

    形式一 应用 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]

    下面的 slice 联合 call 为什么能够在扭转 this 后能够将 arguments 转化成数组?咱们来模仿手写实现一下 slice,就晓得外面的原理了

    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]
    通过 this 获取调用 mySlice 的数组,再通过 length 属性遍历造成一个新的数组返回。所以扭转this 指向 arguments 再通过 arguments.length 遍历返回一个新的数组,便实现了将类数组转化成数组了。

来思考一下字符串能够转化成数组吗?

let a = [].slice.call('stringToArray')console.log(a)  // ["s", "t", "r", "i", "n", "g", "T", "o", "A", "r", "r", "a", "y"]
同样也是能够的,理由同上。至于字符串(值类型)为什么被 this 指定,能够来看看这篇文章 [面试 | call,apply,bind 的实现原理和面试题]()

形式二 应用 ES6 的扩大运算符 ...

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

形式三 Array.from()

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