「千字文」JS常用遍历方法比对

453 阅读4分钟

前言

我从来没有想过有一天会被遍历的问题问的头破血流,尴尬的不知道该说什么,平时在开发中时刻在用的遍历,没想到内部的学问会这么多。

对前端而言,常遇到的引用数据类型就是数组和对象,对这两个数据类型的遍历方法也是最多的,为什么要有这么多遍历的方法,这就是本章要探讨的问题。

for 循环

for 循环是最传统的遍历也是兼容性最好的,它不止可以遍历数组也可以遍历字符串。

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

for 循环相对其他循环的好处,就是可以随时终止,可以在内部使用 continue 和 break 关键词,在不满足条件时,可以终止或者跳过此次循环。

forEach、map 和 filter

forEach

forEach 只能应用于数组,它为每个元素提供一个 callback 函数,该函数中接受三个参数 currenValue、index、array,分别为当前元素、当前元素下标和当前数组,后两者为可选值。

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

forEach 的返回值总是 undefined,所以其不能链式调用,并且其不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。

map

map 同样只能应用于数组,它为每个元素提供一个 callback 函数,该函数中接受三个参数 currenValue、index、array,分别为当前元素、当前元素下标和当前数组,后两者为可选值。

let arr = [1, 2, 3, 4];
let newArr = arr.map((item, i, arr) => {
  console.log(item, i, arr);
})

map 的返回值是一个新的数组,当你不打算使用返回的新数组却使用map 是违背设计初衷的,请用 forEach 或者 for-of 替代。

有两种情况下是不该使用 map 的:

  1. 你不打算使用返回的新数组
  2. 你没有从回调函数中返回值

同样 map 与 forEach 相同不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。

filter

filter 同样只能应用于数组,它为每个元素提供一个 callback 函数,该函数中接受三个参数 currenValue、index、array,分别为当前元素、当前元素下标和当前数组,后两者为可选值。

let arr = [1, 2, 3, 4];
let newFilterArr = arr.filter((item, i, arr) => {
  return item > 1;
})

filter 返回一个过滤后的新数组,如果没有一个元素通过过滤条件则返回一个空数组。

filter 不会改变原数组,它返回过滤后的新数组。

小结

  • 三者除了抛出异常外,是没有办法中止或者跳出循环的。
  • map 和 filter 都会返回一个新数组

reduce

这个就不过多阐述了,可以看我之前写的「查缺补漏」女朋友问我能不能给她讲讲reduce

every 和 some

every

every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

若收到一个空数组,此方法在一切情况下都会返回 true。

let arr = [1, 2, 3, 4];
let boolean = arr.every((item) => item < 5);

some

some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。

如果用一个空数组进行测试,在任何情况下它返回的都是 false。

let arr = [1, 2, 3, 4];
let boolean = arr.some((item) => item < 5);

小结

  • 二者的返回值都是 Boolean 值,every 是数组内全部都通过返回 true,some 是数组内至少有1个元素通过就返回 true
  • 二者都可被中断
  • 二者都不会改变原数组

find 和 findIndex

find

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

let arr = [1, 2, 3, 4];
let element = arr.find((item) => item < 3);

findIndex

findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。

let arr = [1, 2, 3, 4];
let index = arr.findIndex((item) => item < 3)

小结

  • 二者都不改变原数组
  • 二者都可被中断
  • find 返回满足条件的元素值,findIndex 返回满足条件的元素索引

for...of 和 for...in

for...of

for...of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

可迭代对象,即该对象必须实现 iterator 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator 键(key)的属性。

Object 没有内置的 iterator 属性,所以 for...of 无法对其进行遍历。

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

for...in

for...in语句以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性,即其可遍历自身可枚举属性还可以遍历其原型上的可枚举属性。

如果想要仅枚举自身属性的话,那么使用 getOwnPropertyNames() 或执行 hasOwnProperty() 来确定某属性是否是对象本身的属性也能使用propertyIsEnumerable)。

通常来说 for...in 应该是用于调试,可以更方便的去检查对象属性,所以是为遍历对象属性而构建的,不建议与数组一起使用。

let obj = {a: 1, b: 2, c: 3};
for (let key in obj) {
  console.log("obj." + key + "=" + obj[key])
}

接下来看一下 hasOwnProperty() 的作用

let obj = {a: 1, b: 2, c: 3};
function Person() {
  this.name = 'hanmeimei';
}
Person.prototype = obj;
let instance = new Person();
for (let key in instance) {
  console.log("instance." + key + "=" + instance[key])
} 
Output: instance.name = hanmeimei 
        instance.a = 1
        instance.b = 2
        instance.c = 3
for (let key in instance) {
  if (instance.hasOwnProperty(key)) {
    console.log("instance." + key + "=" + instance[key])
  }
} 
Output: instance.name=hanmeimei

小结

  • for...of 是对可迭代对象进行遍历,for...in 是遍历一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性
  • 二者都可以通过 break、continue、throw 和 return 来终止遍历

Object.keys、Object.values、Object.entries 和 Object.getOwnPropertyNames

Object.keys

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。

let result = Object.keys({3: 'a', 1: 'b', 2: 'c'});
Output: ['1', '2', '3'];

其返回值是一个表示给定对象的所有可枚举属性的字符串数组,,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。

Object.values

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

let result = Object.values({3: 'a', 1: 'b', 2: 'c'});
Output: ['a', 'b', 'c'];

其返回值是一个包含对象自身的所有可枚举属性值的数组。

Object.entries

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)

const obj = {a: 1, b: 2};
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key}: ${value}`)
}
Output: a: 1
        b: 2

// Object 转 Map
const obj = {a: 1, b: 2};
const map = new Map (Object.entries(obj));
Output: Map(2) {'a' => 1, 'b' => 2}

其返回值是给定对象自身可枚举属性的键值对数组。

Object.getOwnPropertyNames

Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

const my_obj = Object.create({}, {
  getFoo: {
    value: function() { return this.foo; },
    enumerable: false
  }
});
my_obj.foo = 1;

console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]

其返回值是一个在给定对象上找到的自身属性对应的字符串数组。该数组对元素是 obj 自身拥有的枚举或不可枚举属性名称字符串。

小结

  • Object.keys 和 for...in 都是遍历可枚举属性,但是两者却有着不同,for...in 除了遍历自身的枚举属性外,还会遍历原型链上面的可枚举属性,Object.keys 只能遍历自身的可枚举属性。
  • Object.getOwnPropertyNames 与 Object.keys 和 for...in 的区别在于,其可以遍历不可枚举的属性,但是不会遍历原型上的属性
  • Object.values 返回的是对象自身可枚举的值
  • Object.entries 返回的是对象自身可枚举的键值对,可用于对象转集合

补充:for...in 的遍历顺序

在上一小节中,Object 的几个方法都有说明遍历顺序与 for...in 的顺序相同,那 for...in 的遍历顺序究竟是什么?

ECMAScript规定了对象的遍历顺序是由对象定义时属性的书写顺序决定的,但是在实际应用中却发现了异常。如下所示

let result = Object.keys({3: 'a', 1: 'b', 2: 'c'});
Output: ['1', '2', '3'];

按理说应该输出 ['3', '1', '2'],但是实际却输出 ['1', '2', '3']

这是因为 Chrome Opera 中使用的是 ECMA-262 第五版,for-in 语句对象属性遍历的顺序是没有被规定的时会遵循一个规律,它们会先提取所有 key 的 parseFloat 值为非负整数的属性,然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。其它浏览器使用的是 ECMA-262 第三版则完全按照对象定义的顺序遍历属性。

小结

总结比对了遍历的方法和差异,希望在今后的开发中能使用合适的遍历方法,减少无用的遍历,毕竟会造成大量的消耗,哪些写的不对的希望大家补充!!!

后语

觉得还可以的,麻烦走的时候能给点个赞,大家一起学习和探讨!