大家好,我是林一一,这是一篇比拟 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 = 0
while(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]