前言
首先这篇文章输出的思想目的,是为了自己将最近几年学习到的数组知识进行重新输出整理。
第一部分的数组基础知识是我早期以笔记的形式进行书写,字迹可能会比较潦草,大家看不懂的话请见谅,哈哈。第二部分是从es6到es10的近期数组的相关扩展,后期如果还有什么关于数组的新知识,我会在这里及时更新的。拓展的第三部分和第四部分则分别是对数组的遍历方式和拷贝方式进行的一个总结,方便大家对整体有一个清晰的认识。第五部分则是我自己又重新读了一遍阮一峰的es6的一些心得体会,大家不敢兴趣的可以忽略不计,只是自己的一个平时的记录。
拓展一:数组的基础知识
1)数组的常用方法
改变原数组:push、pop、shift、unshift、sort、reverse、splice
不改变原数组:concat、join --> split、toString、slice
详细基础用法见图:
拓展二:数组的扩展
一:ES6数组的扩展
1.Array.from
1.举例判断是不是伪数组
let div = document.querySelectorAll('img')
// 怎么判断这个是不是伪数组呢
// 1. instanceof
console.log(div instanceof Array) // 看是否返回true
// 2. push一下
div.push(123) // 会报错
注意:伪数组具备两个特征,1. 按索引方式储存数据 2. 具有length属性;如:
let arrLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
}
2.介绍下伪数组:
在 JavaScript 的世界里有些对象被理解为数组,然而缺不能使用数组的原生 API,比如函数中的 arguments、DOM中的 NodeList等。当然,还有一些可遍历的对象,看上去都像数组却不能直接使用数组的 API,因为它们是伪数组(Array-Like)。要想对这些对象使用数组的 API 就要想办法把它们转化为数组,传统的做法是这样的:
let args = [].slice.call(arguments);
let imgs = [].slice.call(document.querySelectorAll('img'));
基本原理是使用 call 将数组的 api 应用在新的对象上,换句话说是利用改变函数的上下文来间接使用数组的 api。在 ES6 中提供了新的 api 来解决这个问题,就是 Array.from,代码如下:
let args = Array.from(arguments);
let imgs = Array.from(document.querySelectorAll('img'));
其他拓展:
语法:Array.from(arrayLike[, mapFn[, thisArg]])
| 参数 | 含义 | 必选 |
|---|---|---|
| arrayLike | 想要转换成数组的伪数组对象或可迭代对象 | Y |
| mapFn | 如果指定了该参数,新数组中的每个元素会执行该回调函数 | N |
| thisArg | 可选参数,执行回调函数 mapFn 时 this 对象 | N |
看了这几个参数至少能看到 Array.from 还具备 map 的功能,比如我们想初始化一个长度为 5 的数组,每个数组元素默认为 1,之前的做法的写法:
let arr = Array(6).join(' ').split('').map(item => 1)
// [1,1,1,1,1]
使用 Array.from 就会简洁很多。
Array.from({
length: 5
}, function() {
return 1
})
2.Array.of()
原始new Array的用法
// new Array里的参数数量不同,表现的结果会有不同
new Array(1,2)
// [1,2]
new Array(3)
// [empty x 3]
这个时候可以引入Array.of()这种用法
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
其他应用场景
let arr = Array.of(1,true,'imooc',[1,2,3],{name: 'xiecheng'})
console.log(arr)
//[1, true, "imooc", Array(3), {...}]
3.Array.prototype.copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
(记忆点:第一个参数是目标位置,第二个参数是从哪个位置开始读取数据,第三个参数从哪读取数据结束)
语法:arr.copyWithin(target, start = 0, end = this.length)
| 参数 | 含义 | 必选 |
|---|---|---|
| target | 从该位置开始替换数据。如果为负值,表示倒数 | Y |
| start | 从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算 | N |
| end | 到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算 | N |
let arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(1, 3))
// [1, 4, 5, 4, 5]
4.Array.prototype.fill()
场景一:可以初始化一个长度固定,元素为指定值的数组,然后全部用fill填充。
let arr = new Array(3).fill(7)
console.log(arr)
// [7,7,7]
let arr = [1,2,3,4,5]
arr.fill(0)
console.log(arr)
// [0,0,0,0,0]
场景二:fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
这个操作是将 array 数组的第二个元素(索引为1)到第三个元素(索引为2)内的数填充为 0,不包括第三个元素,所以结果是 [1, 0, 3, 4]
let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]
二:ES7数组的扩展
1.Array.prototype.includes()
- 在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find(function(item) {
return item === 2
}))
console.log(array1.filter(function(item) {
return item === 2
}).length > 0)
- includes可以接收两个参数
要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es7', 1)) // true
console.log(arr.includes('es7', 2)) // false
console.log(arr.includes('es7', -1)) // false
console.log(arr.includes('es7', -2)) // true
3)includes和indexOf的比较
注意点1:
es5中通常会用indexOf,数组里根本找不到NaN
let arr = [1,2,3,NaN]
console.log(arr.indexOf(NaN))
// -1
es6中用includes,它可以检测出数组里是否有NaN
let arr = [1,2,3,NaN]
console.log(arr.includes(NaN))
// true
注意点2:
只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的.
const arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
总结
如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用includes()。如果想获取一个值在数组中的位置,那么只能使用indexOf方法。
三:ES10数组的扩展
1.Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
语法:const newArray = arr.flat(depth)
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]
2.Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]
下面举个例子
let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
拓展三:数组遍历方式
一. es5中数组的遍历方式
1. for循环
let arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
2.forEach() 没有返回值
forEach 的代码块中不能使用 break、continue,它会抛出异常。
let arr = [1, 2, 3, 4, 5]
// 会抛出异常信息报错
arr.forEach(function(elem, index, array) {
if (arr[i] == 2) {
continue
}
console.log(elem, index)
})
可以更改为以下写法:
[1, 2, 3, 4, 5].forEach(function(i) {
if (i === 2) {
return;
} else {
console.log(i)
}
})
3.map() 返回新的数组,每个元素为调用func的结果
let arr = [1, 2, 3, 4, 5]
let result = arr.map(function(value) {
value += 1
console.log(value)
return value
})
console.log(arr, result)
// [1, 2, 3, 4, 5]
// [2, 3, 4, 5, 6]
4.filter() 返回符合func条件的元素数组
let arr = [1, 2, 3, 4, 5]
let result = arr.filter(function(value) {
console.log(value)
return value == 2
})
console.log(arr, result)
// [1, 2, 3, 4, 5]
// [2]
5.some() 返回boolean,判断是否有元素符合func条件
let arr = [1, 2, 3, 4, 5]
let result = arr.some(function(value) {
console.log(value)
return value == 4
})
console.log(arr, result)
// [1, 2, 3, 4, 5]
// true
6.every() 返回boolean,判断每个元素都符合func条件
同样完成刚才的目标,使用 every 遍历就可以做到 break 那样的效果,简单的说 return false 等同于 break,return true 等同于 continue。如果不写,默认是 return false。
every 的代码块中不能使用 break、continue,它会抛出异常。
let arr = [1, 2, 3, 4, 5]
let result = arr.every(function(value) {
console.log(value)
return value == 2
})
console.log(arr, result)
// [1, 2, 3, 4, 5]
// false
7.reduce()
举例三种应用场景
1----累加器
let sum = arr.reduce(function(prev, cur, index, array) {
return prev + cur
}, 0)
console.log(sum)
2----求数组最大值
let max = arr.reduce(function(prev, cur) {
return Math.max(prev, cur)
})
console.log(max)
3----数组去重
let res = arr.reduce(function(prev, cur) {
prev.indexOf(cur) == -1 && prev.push(cur)
return prev
}, [])
console.log(res)
8.for in 循环
虽然下面的遍历似乎没什么问题,但是如果 array 有自定义属性,你发现也会被遍历出来(显然不合理)。
for (var index in array) {
console.log(array[index]);
}
这是因为 for...in 是为遍历对象创造的({a:1, b:2}),不是为数组设计的。
注意:for...in不能用于遍历数组。 for...in代码块中不能有 return,不然会抛出异常。
let arr = [1, 2, 3, 4, 5]
Array.prototype.foo = function(){
console.log('foo')
}
for (var index in array) {
console.log(array[index]);
}
// 打印如下,显然这么遍历不太合理
// 1 2 3 4 5
// foo f() {
// console.log('foo')
//}
二. es6中数组的遍历方式
1.for of循环
注意:for...of是支持 break、continue、return的,所以在功能上非常贴近原生的 for。
for (let val of [1, 2, 3]) {
console.log(val);
}
// 1,2,3
let arr = [1,2,3,4,5]
for (let item of arr.values()) { // 值
console.log(item)
}
for (let item of arr.keys()) { // 索引
console.log(item)
}
for (let [index, item] of arr.entries()) {
console.log(index, item)
}
看下这个伪代码,of 后面是 iterable 既不是 for 循环规定的 array,也不是 for...in 规定的 Object,而是 iterable。如果查查 iterable 的含义就很直观的感受到 for...of 遍历的是一切可遍历的元素(数组、对象、集合)等,不要小瞧这个功能,因为在 ES6 中允许开发者自定义遍历,换句话说任何数据结构都可以自定义一个遍历,这个遍历是不能被 for、for...in 理解和实现的。
for (variable of iterable) {
xxx
}
2.Array.prototype.find()
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
let array = [5, 12, 8, 130, 44];
let found = array.find(function(element) {
return element > 10;
});
console.log(found);
// 12
3.Array.prototype.findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。
let array = [5, 12, 8, 130, 44];
let found = array.findIndex(function(element) {
return element > 10;
});
console.log(found);
// 1
拓展四:拷贝一个数组的一些总结方法
const numbers = [1, 2, 3, 4, 5];
const copy1 = numbers.slice();
// 使用Array.slice方法
const copy2 = Array.from(numbers);
// 使用 Array.concat 方法
const copy3 = numbers.concat();
// 使用Array.copyWithin
const copy4 = numbers.copyWithin();
// 使用Array.map方法
const copy5 = numbers.map((num) => num);
// 使用Array.from 方法
const copy6 = Array.from(new Set(numbers));
// 使用展开操作符
const copy7 = [...numbers];
// 使用 Array.of 方法和展开操作符
const copy8 = Array.of(...numbers);
// 使用 Array 构造函数和展开操作符
const copy9 = new Array(...numbers);
// 使用解构
const [...copy10] = numbers;
// 使用 Array.push 方法和展开操作符
let copy11 = [];
copy11.push(...numbers);
// 使用 Array.unshift 方法和展开操作符
let copy12 = [];
copy11.unshift(...numbers);
// 使用 Array.forEach 方法和展开操作符
let copy13 = [];
numbers.forEach((value) => copy13.push(value));
// 使用 for 循环
let copy14 = [];
for (let i = 0; i < numbers.length; i++) {
copy14.push(numbers[i]);
}
// 使用 Array.reduce 方法
const copy = numbers.reduce((acc, x) => {
acc.push(x);
return acc;
}, []);
// 使用古老的 apply 方法
let copy15 = [];
Array.prototype.push.apply(copy15, numbers);
拓展五:阮一峰es6数组的扩展学习收获
1.与解构赋值结合注意点
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
// 错误用法
const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错
2.Array.from学习收获
应用一:将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于\uFFFF的 Unicode 字符,算作两个字符的 bug。
function countSymbols(string) {
return Array.from(string).length;
}
应用二:Array.from的第二个参数,作用类似于map方法,用来对每个元素处理然后将处理后的值放入返回的数组中。
// 例子一:
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
// 例子二:
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]
// 例子三:
function typesOf () {
return Array.from(arguments, value => typeof value)
}
typesOf(null, [], NaN)
// ['object', 'object', 'number']
// 例子四:
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']
3.数组实例的fill
注意:如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
new Array(3).fill(7)
// [7, 7, 7]
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]
4.数组实例的 includes()
indexOf方法有两个缺点:
一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。
NaN === NaN (false)
[NaN].indexOf(NaN)
// -1
[NaN].includes(NaN)
// true
5.数组的空位
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach(),filter(),reduce(),every()和some()都会跳过空位。map()会跳过空位,但会保留这个值join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
ES6 则是明确将空位转为undefined。
**注意:**由于空位的处理规则非常不统一,所以建议避免出现空位。
6.Array.prototype.sort() 的排序稳定性
ES2019 明确规定,
Array.prototype.sort()的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。
稳定排序实例:
const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]
参考资料
es6.ruanyifeng.com/#docs/array