# JavaScript数组

# 1. 数组循环

# 1.1 for循环

简洁明了,通俗易懂,可能需要多写点代码,多定义几个变量

// 使用 for 循环遍历数组并修改元素值
const arr = [1, 2, 3, 4, 'a'];
for (let i = 0, len = arr.length; i < len; i++) {
  arr[i] = arr[i] * 2;
}
console.log(arr); // [2, 4, 6, 8, NaN]
1
2
3
4
5
6

# 1.2 forEach循环

接收两个参数,第一个参数是在每一项上运行的函数(拥有三个参数),第二个参数「可选的」是运行该函数的作用域对象(影响this的值),return不能中断函数继续执行,所以没有返回值,不能改变原数组,使用方便一般涌来代替for,但是没for性能高,而且有兼容性(IE6-8)

除抛出异常之外,没有其他方法可以停止或中断循环。如果您需要这种行为,则该forEach()方法是错误的工具

// 使用 forEach 循环遍历数组,但不会修改原数组
const arr = [1, 2, 3, 4, 'a'];
arr.forEach((value, index, array) => {
  return value * 2;
});
console.log(arr); // [1, 2, 3, 4, "a"]
1
2
3
4
5
6

# 1.3 map循环

基本用法和foreach相同,不同的是可以return返回值,但是不改变原数组,一般用来修改数组的值从而映射为一个新数组。

// 使用 map 循环遍历数组并返回新数组
const arr = [1, 2, 3, 4, 'a'];
const arrResult = arr.map((value, index, array) => {
  return value * 2;
});
console.log(arrResult); // [2, 4, 6, 8, NaN]
1
2
3
4
5
6

# 1.4 filter循环

顾名思义是"过滤",就是去掉不想要的值,return true为想要的值,return false则为去掉的值,一般用来过滤一个数组,不改变原来的数组。

// 使用 filter 循环过滤数组并返回新数组
const arr = [1, 2, 3, 4, 'a'];
const arrResult = arr.filter((value, index, array) => {
  return typeof value === 'string';
});
console.log(arrResult); // ["a"]
1
2
3
4
5
6

# 1.5 reduce循环

可以实现一个累加器的功能,将数组的每个值(从左到右)累加起来,不同的是有四个参数,prev表示前两个值的和(没有定义初始值时为第一个值),next为后一个值。

// 使用 reduce 循环累加数组元素
const arr = [1, 2, 3, 4, 'a'];
const arrResult = arr.reduce((prev, next, index, array) => {
  return prev + next;
});
console.log(arrResult); // 10a
1
2
3
4
5
6
// 使用 reduce 循环获取数组中的最大值
const arr = [{ name: 'liam', age: 24 }, { name: 'tom', age: 29 }];
const age = arr.reduce((prev, next) => {
  if ((prev.age) > (next.age)) {
    return prev;
  }
  return next;
});
console.log(age); // { name: 'tom', age:29 }
1
2
3
4
5
6
7
8
9

# 1.6 some循环

some:类似于filter,不同的是返回值为Boolean,不是筛选一个新的数组,而是筛选有没符合条件的值,只要有一个值满足即立刻返回true,不再继续执行,否则返回false。

// 使用 some 循环检查数组中是否有元素满足条件
const arr = [1, 1, 1, 0, 1];
const arrResult = arr.some((value, index, array) => {
  return value === 0;
});
console.log(arrResult); // true
// arr存储问题答案【是】和【否】,上面验证是否答案不全部为【是】
1
2
3
4
5
6
7

# 1.7 every循环

类似于some,不同的是找到符合条件的值会继续执行,如果每个值都满足条件才会返回true,否则就是false

// 使用 every 循环检查数组中是否所有元素都满足条件
const arr = [1, 1, 1, 0, 1];
const arrResult = arr.every((value, index, array) => {
  return value === 1;
});
console.log(arrResult); // false
// arr存储问题答案【是】和【否】,上面验证是否答案全部为【是】
1
2
3
4
5
6
7

# 1.8 indexOf循环

数组中的这个方法和字符串中的几乎一样,都是找到一个满足条件的值就不继续执行了,返回值是满足条件值的下标,否则返回-1

// 使用 indexOf 查找数组中元素的索引
const arr = [1, 2, 3, 4, 'a'];
console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(6)); // -1
1
2
3
4

# 1.9 lastIndexOf循环

类似于indexOf,不同的是查找方向是从后向前

// 使用 lastIndexOf 从后向前查找数组中元素的索引
const arr = [1, 'a', 2, 3, 4, 'a'];
console.log(arr.lastIndexOf('a')); // 5
console.log(arr.lastIndexOf(1)); // 0
1
2
3
4

# 1.10 for...in循环

以任意顺序来遍历一个对象除了Symbol类型外的可枚举属性

// 使用 for...in 循环遍历数组的索引
const arr = [1, 'a', 2, 3, 4, 'a'];
// eslint-disable-next-line guard-for-in
for (const i in arr) {
  console.log(i); // 0, 1, 2, 3, 4, 5
}
1
2
3
4
5
6
  • 遍历是无序的
  • 在可枚举属性里面,不遍历Symbol类型

# 1.11 for...of循环

JavaScript 原有的for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值,但是不能循环对象,一般用来循环数组。

// 使用 for...of 循环遍历数组的元素
const arr = [1, 'a', 2, 3, 4, 'a'];
for (const i of arr) {
  console.log(i); // 1, a, 2, 3, 4, a
}
1
2
3
4
5

# 1.12 includes包含

includes是EJS6新语法,判断数组是否包含目标,可以用于查找NAN。

// 使用 indexOf 和 includes 判断数组中是否包含某个元素
const ary = [1];
if (ary.indexOf(1) !== -1) {
  console.log('数组存在1');
}
if (ary.includes(1)) {
  console.log('数组存在1');
}
1
2
3
4
5
6
7
8
方法 返回值 判断存在NAN 空值判断
includes true/false 判断为undefined
indexOf 大于等于-1的整数 判断为空
// 比较 includes 和 indexOf 对稀疏数组和 NaN 的处理
// eslint-disable-next-line no-sparse-arrays
[1, 2,, NaN].includes(undefined); // true
// eslint-disable-next-line no-sparse-arrays
[1, 2,, NaN].includes(null); // false

[1, 2, NaN].includes(NaN); // true
// eslint-disable-next-line no-sparse-arrays
[1, 2,, NaN].indexOf(undefined); // -1
// eslint-disable-next-line no-sparse-arrays
[1, 2,, NaN].indexOf(null); // -1

[1, 2, NaN].indexOf(NaN); // -1
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. 差异分析

# 2.1 for in、Object.keys、Object.getOwnPropertyNames

方法 遍历原型链属性 遍历不可枚举的属性 遍历Symbol类型
for in
Object.keys
Object.getOwnPropertyNames
Reflect.ownKeys

Object.keys的效果和for in+hasOwnProperty的效果是一样的

# 2.2 forEach、for

# 2.2.1 forEach 的参数

arr.forEach((self,index,arr) =>{},this)

  • self: 数组当前遍历的元素,默认从左往右依次获取数组元素。
  • index: 数组当前元素的索引,第一个元素索引为0,依次类推。
  • arr: 当前遍历的数组。
  • this: 回调函数中this指向。
// forEach 的参数示例
const arr = [1, 2, 3, 4];
const person = {
  name: '技术直男星辰',
};
arr.forEach(function (self, index, arr) {
  console.log(`当前元素为${self}索引为${index},属于数组${arr}`);
  console.log(this.name += '真帅');
}, person);
1
2
3
4
5
6
7
8
9

# 2.2.2 forEach 的中断

在js中有break return continue 对函数进行中断或跳出循环的操作,我们在 for循环中会用到一些中断行为,对于优化数组遍历查找是很好的,但由于forEach属于迭代器,只能按序依次遍历完成,所以不支持上述的中断行。

// forEach 中的中断行为
[1, 2, 3, 4].forEach((self, index) => {
  if (self === 2) {
    // break; // 报错 Uncaught SyntaxError: Illegal break statement
    return; // 并不会报错,但是不会生效 等同continue
  }
  console.log(self); // [1,3,4] return 跳过当前循环
});
1
2
3
4
5
6
7
8

如果我一定要在 forEach 中跳出循环呢?其实是有办法的,借助try/catch

// 使用 try/catch 在 forEach 中实现中断
try {
  [1, 2, 3, 4].forEach((self, index) => {
    // 跳出条件
    if (self === 3) {
      throw new Error('LoopTerminates');
    }
    // do something
    console.log(self);
  });
} catch (e) {
  if (e.message !== 'LoopTerminates') throw e;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

而可以跳出轮询的遍历有以下几个:

  • for循环
  • for...in/for...of
  • Array.prototype.every()
  • Array.prototype.some()
  • Array.prototype.find()
  • Array.prototype.findIndex()

# 2.2.3 forEach 删除自身元素,index不可被重置

在 forEach 中我们无法控制 index 的值,它只会无脑的自增直至大于数组的 length 跳出循环。所以也无法删除自身进行index重置

// forEach 中 index 不可被重置
[1, 2, 3, 4].forEach((item, index) => {
  console.log(item); // 1 2 3 4
  index += 1;
});
1
2
3
4
5
// 在 forEach 中修改数组可能导致元素被跳过
const numbers = [1, 2, 3];
numbers.forEach((item, index, numbers) => {
  console.log(item); // 1 2 3
  if (item === 3) {
    numbers.push(4);
  }
});
console.log(numbers); // [1, 2, 3, 4]

// 如果在轮询中修改了Array,某些元素可能会被轮询跳过
numbers.forEach((item, index, numbers) => {
  console.log(item); // 1 2 4
  if (item === 2) {
    numbers.shift();
  }
});
console.log(numbers); // 2 3 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3. 伪数组

# 3.1 什么是伪数组

  • 伪数组是一个含有length属性的对象。但是length属性不是动态的,不会随着成员的变化而改变
  • 它是按照索引的方式存储数据0: xxx, 1: xxx, 2: xxx...
  • 它并不具有数组的一些方法。不具有数组的push(), forEach()等方法

# 3.2 常见的伪数组

  • 函数中的的 arguments 对象
  • 字符串String对象
  • jQuery中通过 $() 获取的DOM元素集
  • 调用getElementsByTagName,document.childNodes之类

# 3.3 伪数组转换为数组

# 3.3.1 遍历添加入一个空数组

// 通过遍历将伪数组转换为数组
function makeArray(arrLike) {
  const arr = [];
  for (let i = 0, len = arrLike.length; i < len ; i++) {
    arr.push(arrLike[i]);
  }
  return arr;
}
const t = new String('abcd');
console.log(makeArray(t)); // ["a", "b", "c", "d"]
1
2
3
4
5
6
7
8
9
10

# 3.3.2 利用数组的slice()方法 【推荐】

// 使用 slice 方法将伪数组转换为数组
const t = new String('abcd');
[].slice.call(t); // ["a", "b", "c", "d"]
Array.prototype.slice.apply(t); // ["a", "b", "c", "d"] 该方法
1
2
3
4

这个返回的数组中,不会保留索引值以外的其他额外属性。 模拟一下slice()的内部实现

// slice 方法的内部实现
Array.prototype.slice = function(start, end) {
  // eslint-disable-next-line no-array-constructor
  const arr = new Array();
  const start1 = start || 0;
  const end1 = end || this.length;
  for (let i = start1; i < end1; i++) {
    arr.push(this[i]);
  }
  return arr;
};
1
2
3
4
5
6
7
8
9
10
11

# 3.3.3 使用ES6中Array.from方法

// 使用 Array.from 方法将伪数组转换为数组
const t = new String('abcd');
Array.from(t); // ["a", "b", "c", "d"]
1
2
3

得到的结果与第二种方法类似,只保留索引值内的属性。 Array.from() 方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。

# 4. 数组应用

# 4.1 数组分页

// 使用嵌套 for 循环实现数组分页
function arrTrans(num, arr) { // 一维数组转换为二维数组
  const arrResult = []; // 声明数组
  for (let i = 0; i < Math.ceil(arr.length / num); i++) {
    arrResult[i] = []; // 声明该数组第一个元素为一个数组
    for (let j = 0; j < num && j + num * i < arr.length; j++) {
      arrResult[i].push(arr[j + num * i]);
    }
  }
  return arrResult;
}
1
2
3
4
5
6
7
8
9
10
11

优化之后为

// 使用 forEach 实现数组分页
function arrTrans(num, arr) { // 一维数组转换为二维数组
  const iconsArr = []; // 声明数组
  arr.forEach((item, index) => {
    const page = Math.floor(index / num); // 计算该元素为第几个素组内
    if (!iconsArr[page]) { // 判断是否存在
      iconsArr[page] = [];
    }
    iconsArr[page].push(item);
  });
  return iconsArr;
}
1
2
3
4
5
6
7
8
9
10
11
12

最终结果:

// 使用 splice 实现数组分页
function arrTrans(num, arr) {
  const newArr = [];
  while (arr.length > 0) {
    newArr.push(arr.splice(0, num));
  }
  return newArr;
}
1
2
3
4
5
6
7
8

# 5. 参考

彻底搞懂并会用数组之循环 -- 新哥·lewis (opens new window)

Last Updated: 8/19/2025, 4:28:59 PM