我正在参加「掘金·启航计划」
读这篇文章,我需要花多长时间?
字数:3082 时间:10min
读这篇文章,我能学到什么?
🍪 es5数组与es6数组的比较,分析了各遍历的特点、性能、基本实现。 for...of、for、forEach、map、filter、some、every、reduce、reduceRight、for...in等。
🍪 面试题:
1.map与forEach本身能终止循环吗?
2.详细说说for循环如何污染全局变量?
3.for...in和for...of有什么区别?
4.在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么?
5.在循环 for、for-in、forEach、for-of、map性能如何?
ES5数组 VS ES6数组
1 ES5数组遍历
-
forEach、map、filter、some、every传递的参数有:
🍪 currentValue:必须,当前元素。
🍪 index:可选。当前元素的索引值。
🍪 arr:可选、当前元素所属的数组对象。
🍪 thisValue:可选。传递给函数的值一般用this值,如果这个参数为空,undefined会传递给this值。
// 比如:forEach: arr.forEach((currentValue,index,arr)=>{},thisValue)
-
reduce、reduceRight传递的参数有:
🍪 total:上一次调用回调返回的值,或者提供的初始值。
🍪 currentValue:必须,当前元素。
🍪 index:可选。当前元素的索引值。
🍪 arr:可选、当前元素所属的数组对象。
🍪 initialValue:表示传递给函数的初始值(作为第一次调用callback的第一个参数)
// 比如:reduce: arr.reduce((total,currentValue,index,arr)=>{},initialValue)
1.1 for
-
基本实现:
const arr = [0, 1, 3, 2] for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } console.log(i)// undefined
const arr = [0, 1, 3, 2] for (var i = 0; i < arr.length; i++) { console.log(arr[i]); } console.log(i)// 4
上面的两个遍历有啥区别?细心的你就会发现,上面两个遍历就是let与var的区别。非常叻仔的你就会发现这是作用域的问题(块级作用域与全局作用域)。
🍪 块级作用域:let声明的变量只能在当前作用域内有效,也就是上面的for代码块里有效,所以在for作用外调用的变量是没声明的。
🍪 全局作用域:var定义的
i是for函数的全局变量
,每次递增不会重新声明,由于每次递增没有发现i声明,但是for的全局存在i,此时会通过作用域链去找i,找到的时候for循环已经执行完毕,退出循环了。 -
for性能消耗:
🍪 普通版for循环
let data = new Array(100000).fill(0); // 开始for这个计时器的计时 console.time('for'); for (let i = 0; i < data.length; i++) { } // 结束for这个计时器的计时 console.timeEnd('for'); // 2.5ms
🍪 优化版循环
console.time('for'); for (let i = 0, len = data.length; i < len; i++) { } console.timeEnd('for'); // 2.17ms
1.2 forEach
-
从Array.forEach源码角度去理解forEach:
function myForEach(arr, callback) { let T, k; if (arr === null) { throw new TypeError('this is null or not defined'); } // 用于处理若传入的arr为非数组的情况 const obj = Object(arr); const len = obj.length >>> 0;// 无符号右移:十进制转化为二进制 if (typeof callback !== 'function') { throw new TypeError(`${callback} is not a function`); } // 保证函数参数不止一个 if (arguments.length > 1) { T = callback; } k = 0; while (k < len) { // 如果指定属性在指定的对象或其原型链中,则in运算符返回true // 用于过滤未初始化值 if (k in obj) { const kValue = obj[k]; // kValue, k, obj对应着forEach回调函数3个参数,数组当前项、当前索引、数组对象本身 // call:将callback的this指向其自己的内部 callback.call(T, kValue, k, obj); } k++; } return undefined; } const arr = [1, 2, 3]; myForEach(arr, (item) => { console.log(item); })
-
forEach特点:
🍪 forEach方法
不会改变原数组
,也没有返回值。🍪 forEach无法使用break,continue跳出循环,使用return时,效果和在for循环中使用continue一致。
🍪 forEach方法
无法遍历对象
,仅仅适用于数组
的遍历。🍪 forEach() 对于
空数组是不会执行回调函数的
。 -
forEach的基本实现:
arr.forEach((currentValue,index,arr)=>{},thisValue) const arr = [2, 3, 5, 1, 6]; arr.forEach((item, index, array) => { // array也就是arr console.log(index, item, array); })
-
forEach性能消耗:
console.time('forEach'); data.forEach(item => { }); console.timeEnd('forEach'); // 9.18ms
1.3 map
-
从Array.map源码角度去理解:
Array.prototype.myMap = function (callback) { var T, A, k; if (this == null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } if (arguments.length > 1) { T = arguments[1]; } // 创建数组对象 A = new Array(len); k = 0; while (k < len) { var kValue, mappedValue; if (k in O) { mappedValue = callback.call(T, kValue, k, O); A[k] = mappedValue; } k++; } return A; }; const arrMap = [ { name: '孙悟空', sex: 0, org: '' }, { name: '猪八戒', sex: 0, org: '' }, { name: '唐僧', sex: 0, org: '' }, { name: '沙僧', sex: 0, org: '' }]; arrMap.myMap((item) => { console.log(item); return item; });
-
map的特点:
🍪 map方法不会对空数组进行检测。
🍪 map方法遍历数组是
返回一个新数组,不会改变原数组
。🍪 map方法有返回值,可以return出来,map的回调函数中支持return返回值。
🍪 map方法无法遍历对象,仅适用于数组的遍历。
-
map的基本实现:
let arr = [2, 3, 5, 1, 6]; let newArr = arr.map((item, index, array) => item = item * 2); console.log(arr, newArr); // [2, 3, 5, 1, 6],[4, 6, 10, 2, 12]
-
map性能消耗:
console.time('map'); data.map(item => { }); console.timeEnd('map'); // 20.6ms
1.4 filter
-
filter的特点:
🍪 filter方法会返回一个新的数组,不会改变原数组。
🍪 filter方法不会对空数组进行检测。
🍪 filter方法适用于检测数组。
-
filter的基本实现:
let arr = [2, 3, 5, 1, 6]; let newArr = arr.filter((item, index, array) => item>3); console.log(arr, newArr); // [2, 3, 5, 1, 6],[5,6]
-
filter的特质:
🍪 特别地,filter方法可以用来
移除数组中的undefined、null、NAN等值
。let arr = [1, undefined, 2, null, 3, false, '', 4, 0]; let newArr = arr.filter(Boolean); console.log(newArr);// [1,2,3,4]
-
filter性能消耗:
console.time('filter'); data.filter(item => item < 0); console.timeEnd('filter'); // 16.2ms
1.5 some、every
-
特点:
🍪两个方法都
不会改变数组
,会返回一个布尔值
。🍪两个方法都不
会对空数组进行检测
。🍪两个方法都
仅适用于检测数组
。 -
基本实现:
🍪 some:数组中只要
有一个元素符合判断条件的
,返回true,否则false🍪 every:数组中只有
每个元素都符合判断条件的
就返回true,否则falseconst arr = [1,2,45,-1,0]; arr.some(item=>item<0);// true; arr.every(item=>item<0);// false;
1.6 reduce、reduceRight
-
特点:
🍪 两个方法都不会改变数组。
🍪 两个方法如果添加初始值,就会改变原数组,会将这个初始值放在数组的最后一位。
🍪 两个方法对于空数组是不会执行回调函数的。
-
基本实现:
🍪 reduce:正序遍历,接收一个函数作为累加器,数组中的每个值从左到右开始累加,最终计算为一个值。
let arr = [1, 2, 3, 4] let sum = arr.reduce((prev, cur, index, arr) => { return prev + cur }, 5) console.log(arr, sum);// [1,2,3,4] 15
🍪 reduceRight:与reduce用法一样,但它逆序遍历,数组中每个值从右到左开始累加,最终计算为一个值。
1.7 for...in
-
特点:
🍪 for...in方法不仅会
遍历当前的对象所有的可枚举属性
,还会遍历其原型链上的属性
。 -
基本实现:
const obj = { a:1,b:2,c:3} for(let i in obj){ console.log('键名',i); console.log('键值',obj[i]); }
-
for in性能消耗:
console.time('for in'); for (item in data) { } console.timeEnd('for in'); // 178ms
2 ES6遍历
2.1 find、findIndex
-
find:通过函数内判断的数组的第一个元素的值。
let arr = [2,1,3,5,4]; arr.find(item=>item>2);// 3
-
findIndex:找到数组中符合条件的第一个元素位置。
let arr = [2,1,3,5,4]; arr.findIndex(item=>item>2);// 索引为2
2.2 for...of
-
for of特点:
🍪 for of方法
只会遍历当前对象的属性
,不会遍历其原型链上的属性
。🍪 for of方法适用遍历数组、类数组、字符串、map、set等有迭代器对象的集合。
🍪 for of方法
不支持遍历普通对象
,因为其没有迭代器对象。如果想要遍历一个对象的属性,可以用for in🍪 可以使用break、continue、return来中断循环遍历。
🍪 对于
for of
的循环,可以由break
,contine
或return
终止,这种情况下,迭代器关闭。 -
for of基本实现:
let arr = [ {id:1, value:'hello'}, {id:2, value:'world'}, {id:3, value:'JavaScript'} ] for (let item of arr) { console.log(item) } // 输出结果:{id:1, value:'hello'} {id:2, value:'world'} {id:3, value:'JavaScript'}
-
for of性能消耗:
console.time('for of'); for (item of data) { } console.timeEnd('for of'); // 20ms
2.3 keys()、values()、entries()
- keys() 返回数组的索引值(对象键名)。
- values() 返回数组的元素(对象键值)。
- entries() 返回数组的键值对。
Object.keys()方法返回的数组中的值
都是字符串
,也就是说不是字符串的key值会转化为字符串
结果数组中的属性值都是对象本身可枚举的属性,不包括继承来的属性。
const arr = [
{ name: '孙悟空', sex: 0, org: '' },
{ name: '猪八戒', sex: 0, org: '' },
{ name: '唐僧', sex: 0, org: '' },
{ name: '沙僧', sex: 0, org: '' }];
for (let item of arr.keys()) {
console.log(item);// 0 1 2 3
}
for (let item of arr.values()) {
console.log(item);// 整个数组对象
}
for (let item of arr.entries()) {
console.log(item);// [数组每个对象的索引,数组中的每个对象]
}
面试官会问什么?
1.map与forEach本身能终止循环吗?
它们本身是不能终止循环,而是通过抛出new throw error()
或try...catch
去捕获这个错误才可以终止循环。
let list=[1,2,3,4,5,6];
try{
list.map(item=>{
if(item===3){
throw new Error()
}
console.log(item)
})
} catch {}
// 1 2
es6对这个缺陷做了一个改进就是for of的出现。
2.详细说说for循环如何污染全局变量?
3.for...in和for...of有什么区别?
-
遍历数组时:
🍪 for...in获取到的是每一项的索引值。
🍪 for...of获取到的是每一项的值。
const arr = [ { name: '孙悟空', age: 500, org: '花果山' } ] for (item in arr) { console.log(item) } // 0 for (item of arr) { console.log(item) // {name: '孙悟空',age: 500,org: '花果山'} }
-
遍历对象时:
🍪 for...in获取到的是每一项的key值。
🍪 for...of不能用于遍历对象,会报错,对象数组可以。
const obj = { name: '孙悟空', age: 500, org: '花果山' }; for (item in obj) { console.log(item) } // name age org for (item of obj) { // 报错 console.log(item) }
4.在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么?
了解过js都知道迭代器吧,就是遍历的特性,对于item.next().value进行操作。
🍪 如果原来的值是引用类型,那么iterator.next().value 和 arr[i]表示的是同一个对象,所以可以改变原来的item。
🍪 如果原来的值是基础类型,那么iterator.next().value 和 arr[i]分别指向了一个基础类型的值,所以不会改变原来的item。
-
改变item元素值:
const arr = [ { name: '孙悟空', org: '花果山' }, 9, 'swk', function f() { console.log(3); }, [9, 9, 9, 9], new Date() ]; // 数组元素全变为'西天取经' for (let i = 0; i < arr.length; i++) { arr[i] = '西天取经'; } // arr数组元素没有变 arr.forEach((item) => { item = '西天取经'; }) // arr数组元素没有变 arr.map((item) => { item = '西天取经'; }) // arr数组元素没有变 for (let item in arr) { item = '西天取经'; } // arr数组元素没有变 for (let item of arr) { item = '西天取经'; }
-
改变item元素属性
const arr = [ { name: '孙悟空', org: '花果山' }, 9, 'swk', function f() { console.log(3); }, [9, 9, 9, 9], new Date() ]; // 数组元素如下图所示 for (let i = 0; i < arr.length; i++) { arr[i].org = '西天取经'; } // 数组元素如下图所示 arr.forEach((item) => { item.org = '西天取经'; }) // 数组元素如下图所示 arr.map((item) => { item.org = '西天取经'; }) // arr数组不变 for (let item in arr) { item.org = '西天取经'; } // 数组元素如下图所示 for (let item of arr) { item.org = '西天取经'; }
5.for、for-in、forEach、for-of 、map性能比较如何?
统一执行上面的性能代码得出以下效果:
站在大佬的肩膀上,可以看的更远!
# 在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么? |# JS数组循环的性能和效率分析(for、while、forEach、map、for of)