0%

JavaScript中遍历数组、对象的几种方式

JavaScript中数组和对象均可进行遍历。前端开发时,常会用到对数组、对象以及对象数组进行遍历。

1 普通for循环

与其他语言一致。

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

可以使用变量缓存数组长度,进行优化。

1
2
3
4
let array = [1, 2, 3, 4, 5];
for (let i = 0, len = array.length; i < len; i++) {
console.log(array[i]); // 1 2 3 4 5
}

2 for-in循环

  1. 对于数组,for-in循环遍历的是数组的索引值,例如:

    1
    2
    3
    4
    let array = [1, 3, 7];
    for (const index in array) {
    console.log(index); // 0 1 2
    }

    需要注意:这里的索引为字符串,而非数值,不能用于计算;且for-in会遍历数组所有可枚举属性(包括原型),因此尽量避免使用for-in遍历数组。

    1
    2
    3
    4
    let array = [1, 3, 7];
    for (const index in array) {
    console.log(typeof index); // string string string
    }
  2. 对于对象,for-in循环遍历的是对象的键名,例如:

    1
    2
    3
    4
    5
    6
    7
    let obj = {
    id: 1,
    name: 'Carlo'
    };
    for (const key in obj) {
    console.log(key); // id name
    }

    需要注意:for-in也会遍历到对象原型链上的方法与属性,可以通过hasOwnPropery方法判断属性是否为实例属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Object.prototype.school = 'ZJU';	// 原型属性
    let obj = {
    id: 1,
    name: 'Carlo'
    };
    for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
    console.log(key); // id name 不会输出school
    }
    }

3 for-of循环

  1. 对于数组,for-of循环遍历的是数组的元素值,例如:

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

    for-of循环不会遍历数组的原型方法与属性,因此是最常用来遍历数组的方法。

  2. for-of循环不支持遍历普通对象,但可以遍历类数组对象、Map和Set等。

以上循环均可以使用breakcontinuereturn语句,例如:

1
2
3
4
5
let array = [1, 3, 7];
for (const item of array) {
if (item === 3) continue;
console.log(item); // 1 7
}

4 对象该怎么遍历?

除上文提到的方法外,对象还可以通过以下方式遍历:

  1. 键名用for-in获取,键值用obj[key]获取,会遍历到原型方法与属性。

    1
    2
    3
    4
    5
    6
    7
    let obj = {
    id: 1,
    name: 'Carlo'
    };
    for (const key in obj) {
    console.log(key + ': ' + obj[key]); // id: 1 name: Carlo
    }

    注意:上面第6行取值时不能写obj.key,这时key会被认为是obj的一个属性,而非循环变量中的key;也就是obj.key等同于obj['key']

  2. 使用内建的Object.keys方法,会获得由对象实例属性组成的数组,不包含原型方法与属性。

    1
    2
    3
    4
    5
    6
    7
    let obj = {
    id: 1,
    name: 'Carlo'
    };
    for (const key of Object.keys(obj)) {
    console.log(key + ': ' + obj[key]); // id: 1 name: Carlo
    }

    或使用下文提到的forEach函数:

    1
    2
    3
    4
    5
    let obj = {
    id: 1,
    name: 'Carlo'
    };
    Object.keys(obj).forEach(key => console.log(key + ': ' + obj[key])); // id: 1 name: Carlo
  3. 使用内建的Object.values方法,会获得由对象实例属性值组成的数组,不包含原型方法与属性;使用内建的Object.entries方法,会获得由对象实例属性和属性值组成的数组,不包含原型方法与属性。这两个方法与Object.keys类似,在此不表。

  4. 类数组对象、字符串、Map、Set等可迭代对象拥有迭代器,可以使用for-of遍历。

    1
    2
    3
    4
    let string = 'hello'
    for (let s of string) {
    console.log(s); // h e l l o
    }
  5. 普通对象也可为其添加Symbol.iterator方法,并赋一个生成器函数,以使用for-of遍历:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let obj = {
    id: 1,
    name: 'Carlo',
    [Symbol.iterator]: function* () {
    for (const key of Object.keys(this)) {
    yield this[key];
    }
    }
    };
    for (const value of obj) {
    console.log(value); // 1 Carlo
    }

5 数组的高阶遍历函数

下文提到的遍历函数均用于数组,在开发中经常使用。

5.1 forEach函数

对数组进行遍历,参数为一个匿名回调函数,匿名函数的参数为元素值与索引值(可省略),并且此处索引值为数字格式。

1
2
3
4
let array = [1, 3, 7];
array.forEach(function (value, i) {
console.log(i + ': ' + value); // 0: 1 1: 3 2: 7
})

匿名回调函数可以写成箭头函数,更为精简,下文均使用箭头函数。

1
2
let array = [1, 3, 7];
array.forEach((value, i) => console.log(i + ': ' + value));

forEach功能较弱,甚至不如普通for循环;并且不能使用breakcontinuereturn语句,类似地,其他高阶函数也不能使用(return除外)。

5.2 map函数

map是映射的意思,它对原数组的每个元素都遍历一次,同时返回一个新的值,并放到新数组中,因此其支持return,但不能用return跳出map,下同。

例如,我们想把一个数组变成其两倍,可通过map实现:

1
2
3
let array = [1, 3, 7];
let newArray = array.map(i => i * 2);
console.log(newArray); // [ 2, 6, 14 ]

再例如,对一个对象数组people,我们想得到所有人的id数组,即[1, 2, 3]

1
2
3
4
5
let people = [
{id: 1, name: 'Carlo'},
{id: 2, name: 'Bill'},
{id: 3, name: 'Jack'}
];

如果使用forEach函数,做法如下:

1
2
let peopleId = [];
people.forEach(person => peopleId.push(person.id));

会发现不得不先创造一个新数组。如果使用map则精简很多,因为其本身就会返回一个数组:

1
let peopleId = people.map(person => person.id);

map也常被用在构建二维数组中,如:

1
const dp = new Array(m).fill(0).map(() => new Array(n).fill(0));

5.3 filter函数

filter是过滤的意思,它对原数组的每个元素都遍历一次,同时返回判断结果(布尔值),将为true的元素放到新数组中。

例如,我们想把一个数组中的奇数提取出来,可通过filter实现:

1
2
3
let array = [1, 2, 3, 5, 6, 8];
let newArray = array.filter(i => i % 2); // 数值1会被转换成布尔值true
console.log(newArray); // [ 1, 3, 5 ]

再例如,对对象数组people,我们想把名字中第二个字母为a的人提取出来,可通过filter实现:

1
2
3
4
5
6
7
let people = [
{id: 1, name: 'Carlo'},
{id: 2, name: 'Bill'},
{id: 3, name: 'Jack'}
];
let somePeople = people.filter(person => person.name.charAt(1) === 'a');
console.log(somePeople); // [ { id: 1, name: 'Carlo' }, { id: 3, name: 'Jack' } ]

5.4 find函数

find是寻找的意思,它对原数组的每个元素都遍历一次,同时返回判断结果(布尔值),将第一个为true的元素返回。

例如,对对象数组people,我们想把id为1的人提取出来,通过filter也可实现:

1
2
3
4
5
6
7
let people = [
{id: 1, name: 'Carlo'},
{id: 2, name: 'Bill'},
{id: 3, name: 'Jack'}
];
let somePeople = people.filter(person => person.id === 1);
console.log(somePeople[0]); // { id: 1, name: 'Carlo' }

如果通过for循环则是如下逻辑:

1
2
3
4
5
6
7
8
9
10
11
let people = [
{id: 1, name: 'Carlo'},
{id: 2, name: 'Bill'},
{id: 3, name: 'Jack'}
];
for (let person of people) {
if (person.id === 1) {
console.log(person); // { id: 1, name: 'Carlo' }
break;
}
}

会发现for-if-break是个连续的逻辑,否则会重复遍历。

如果使用find则精简很多,因为其本身就会返回所需的元素:

1
2
3
4
5
6
7
let people = [
{id: 1, name: 'Carlo'},
{id: 2, name: 'Bill'},
{id: 3, name: 'Jack'}
];
let person = people.find(person => person.id === 1);
console.log(person); // { id: 1, name: 'Carlo' }

注意:当filter找不到任何匹配项时,返回空数组;当find找不到任何匹配项时,返回undefined

5.5 reduce函数

reduce可以理解为缩减,它把一个数组逐一缩减为一个值,可通过下面的例子理解。

对下面的对象数组people,现在想知道每个人的分数总和,通过for循环是这样实现的:

1
2
3
4
5
6
7
8
9
10
let people = [
{id: 1, name: 'Carlo', points: 90},
{id: 2, name: 'Bill', points: 95},
{id: 3, name: 'Jack', points: 88}
];
let sum = 0;
for (let person of people) {
sum += person.points;
}
console.log(sum); // 273

reduce函数传入两个参数,一个是回调函数(参数为初始变量与数组元素),另一个是初始变量的值,即:

1
2
3
4
5
6
7
let people = [
{id: 1, name: 'Carlo', points: 90},
{id: 2, name: 'Bill', points: 95},
{id: 3, name: 'Jack', points: 88}
];
let sum = people.reduce((sum, person) => sum + person.points, 0);
console.log(sum); // 273

逻辑是:先把0赋给sum,再对people里的每个person做遍历,执行回调函数,并将每一步的返回值赋给sum

除累加外,我们还可以使用它进行大小比较。例如,我们想返回分数最高的人,可以这样实现:

1
2
3
4
5
6
7
let people = [
{id: 1, name: 'Carlo', points: 90},
{id: 2, name: 'Bill', points: 95},
{id: 3, name: 'Jack', points: 88}
];
let highest = people.reduce((highest, person) => (highest.points || 0) > person.points ? highest : person, {});
console.log(highest); // { id: 2, name: 'Bill', points: 95 }

如果reduce没有给出初始变量的值,数组第一个变量会被赋给初始变量,同时从数组第二个元素开始遍历,注意以下代码的输出结果:

1
2
3
4
5
6
7
8
9
10
11
let arr = [1, 3, 7, 10];
let sum1 = arr.reduce((sum, n, i) => {
console.log([sum, n, i]); // [0,1,0] [1,3,1] [4,7,2] [11,10,3]
return sum + n;
}, 0);
console.log(sum1); // 21
let sum2 = arr.reduce((sum, n, i) => {
console.log([sum, n, i]); // [1,3,1] [4,7,2] [11,10,3]
return sum + n;
});
console.log(sum2); // 21

5.6 结合使用

以上函数可以通过链式编程的思想结合使用。

例如,对一个数组中小于100的数,将其扩大一倍再求和,可以这样实现:

1
2
3
let array = [99, 123, 85, 3, 212, 56];
let t = array.filter(i => i < 100).map(i => i * 2).reduce((sum, i) => sum + i, 0);
console.log(t); // 486