# 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]
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"]
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]
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"]
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
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 }
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存储问题答案【是】和【否】,上面验证是否答案不全部为【是】
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存储问题答案【是】和【否】,上面验证是否答案全部为【是】
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
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
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
}
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
}
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');
}
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
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);
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 跳过当前循环
});
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;
}
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;
});
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
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"]
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"] 该方法
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;
};
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"]
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;
}
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;
}
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;
}
2
3
4
5
6
7
8