基本概念
数组的本质是一个有序的key,value数据对象集合
let arr = [1, 2, 3];
typeof arr; // "object"
数组是对象,但是无法使用点结构获取相应成员值,是因为单独的数值不能作为标识符,只能使用方括号结构
arr.0; // 报错
arr[0];
数组的某个位置是空元素,即两个逗号之间没有任何值,这时就形成了一个空位
let a = [1, , 3];
a.length; // 3
...
// 这个空位只有处于两个逗号之间才有效
let b = [1, , 3, ];
b.length; // 3
...
// 使用delete删除某个数组元素,在相应位置会形成空位
let c = [1, 2, 3];
delete c[1]; // [1, empty, 3]
c[1]; // undefined
// 数组空位和undefined是两个概念,此时如果对数组进行遍历会跳过空位
for (let i in c) {
console.log(i)
};
// 0
// 2
c.length; // 3 数组长度还是3,但是进行遍历时会忽略第二个空位
类数组对象是指某个对象的所有key值都是正整数或者零,并且有length属性,看起来和数组一致
不要使用new Array的形式定义数组,因为会产生数组空位,而数组的方法对空位的处理很不一致,有些方法处理,有些不处理,所以为了避免产生数组空位,建议禁止使用new Array定义数组
扩展运算符
扩展运算符以三个点的形式出现 ... 可以将数组或者对象里面的值展开(使用形式和rest参数类似,不过rest参数是把所传入参数组成一个数组,而扩展运算符如果是操作数组则正好相反,是把一个数组展开,最大的区别也就是在使用方式上,rest参数形式是针对函数定义时的处理,而扩展运算符是针对函数使用时的操作,使用的运算符都是一样的...)
reset参数(形式为...变量名)
- 复制数组
const a1 = [1, 2];
// 方式一:直接赋值
const a2 = a1;
a1[0] = 2;
a2; // [2, 2]
// 方式二:使用扩展运算符
const a2 = [...a1];
a1[0] = 2;
a2; // [1, 2]
原因是因为数组是特殊的对象,直接把一个数组赋值给另外一个数组,此时进行的是传址赋值(就是两个数组指向的是同一个内存区域,所以只有修改其中一个数组值另外一个必须会一起更新),但是如果使用扩展运算符,相当于是重新声明一个有相同元素的数组,此时是在两个内存区域
- 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
const arr4 = arr1.concat(arr2, arr3); // ["a", "b", "c", "d", "e"]
// 扩展运算符
const arr5 = [...arr1, ...arr2, ...arr3]; // ["a", "b", "c", "d", "e"]
// 这两种方式都是浅拷贝,如果操作元素是对象时,要留意
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
a3[0].foo; // 1
a4[0].foo; // 1
a1[0].foo = 2;
a3[0].foo; // 2
a4[0].foo; // 2
- 字符串
扩展运算符如果应用于字符操作时,可以把字符转成真正的数组,还可以识别超出utf16的32位形式
'x\uD83D\uDE80y'.length; // 4
[...'x\uD83D\uDE80y'].length; // 3
let str = 'x\uD83D\uDE80y';
str.split('').reverse().join(''); // 'y\uDE80\uD83Dx' 这是当做四个字符处理
[...str].reverse().join(''); // 'y\uD83D\uDE80x' 按三个字符处理
- 解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5];
rest; // [2, 3, 4, 5]
扩展运算符如果应用到对象解构赋值时会要求不能在其后有其他值和rest参数使用一致
let {x, ...y, z} = {x:1, y:2, z:3};
// Rest element must be last element
对象进行解构赋值时是浅拷贝,并且不会拷贝继承属性和不可遍历属性
let a1 = {a1: 1};
let a2 = Object.create(a1, {
foo: {
value: 'foo',
enumerable: true,
configurable: true,
writable: true,
},
baz: {
value:'foo'
}
});
let {...a3} = a2;
Object.getOwnPropertyDescriptors(a3); // {foo: {…}}
a3.baz; // undefined
a3.a1; // undefined
- 数组展开
// 扩展运算符如果使用在数组中,可以很方便的展开
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6] 把相关数组展开,组成了一个新数组
...
// 使用在函数传参时,会默认变成以逗号隔开的传参数形式
const args = [0, 1];
f(-1, ...args, 2, ...[3]); // 此时传入参数: -1, 0, 1, 2, 3
function f(v, w, x, y, z) {
console.log(v);
console.log(w);
console.log(x);
console.log(y);
console.log(z);
}
扩展运算符还可以放置普通表达式
let x = 1;
const arr = [
...(x > 0 ? ['a'] : []),
'b',
]; // ['a', 'b']
扩展运算符可以简化不少写法
// 求一个数组的最大值
Math.max(...[14, 3, 77]); // 77
// 将一个数组插入到另外一个数组的尾部
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2); // arr1为 [0, 1, 2, 3, 4, 5]
// push接受值方法是没办法使用数组的,如果直接对数组使用push会直接把数组插入
arr2.push(arr1); // arr2为 [3, 4, 5, Array(6)]
Array.from
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
// 类数组对象
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
Array.from(arrayLike); // ["a", "b", "c"]
这个方法可以接收第二个参数,用来表示对转换后的数组,进行遍历处理
Array.from([1, 2, 3], x => x + x); // [2, 4, 6]
Array.from 还可以把字符转为数组
Array.of
Array.of 创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型
Array.of(3, 11, 8); // [3, 11, 8]
// 不管传入值为什么,都会返回数组
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
数组相关属性以及方法
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [7, 8, 9];
let arr4 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let arr5 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let arr6 = [1, 2, 3, 2, 1, 3];
let isEven = function (x) {
return (x % 2 === 0);
}
let isEven2 = function (x) {
console.log(x % 2 === 0);
}
下面方法[0]表示不改变原始数组,[1]表示改变原始数组,每个方法演示上面的数值都重新赋值
- length属性,返回数组的成员数量
arr1.length; // 3
- in运算符可以检查某个key值是否存在(这个方法是对象检查key是否存在,数组是特殊对象所以可用)
2 in arr2; // true 因为arr2有三个数组元素,对应的key值是0,1,2所以key值2存在
4 in arr2; // false
- for...in遍历数组的key值(这个也是对象的方法,数组也只是刚好可以用)
for(let i in arr2) {
console.log(i);
}
// 0
// 1
// 2
...
// arr2改造下
arr2.foo = 'foo'; // arr2: [4, 5, 6, foo: "foo"]再次用for...in循环下
// 0
// 1
// 2
// foo
- Array.isArray判断是否为数组
Array.isArray(arr2); // true
- concat:对数组进行连接,返回一个新数组 [0]
使用扩展运算符
...更好
// 下面输出[1, 2, 3, 4, 5, 6, 7, 8, 9]
// arr1,arr2,arr3不变
arr1.concat(arr2, arr3);
- join: 使用指定符号连接数组数据,返回一个字符串,默认为‘,’ [0]
arr1.join(); // '1,2,3'
arr1.join('-'); // '1-2-3';
- keys: 返回一个包含数组中每个索引键的Array Iterator对象
[...[2,3,4].keys()];
// [0, 1, 2]
- values: 返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值
[...[2,3,4].values()]; // [2, 3, 4]
- pop: 删除并返回数组的最后一个元素 [1]
arr1.pop(); // 3,此时arr1变为[1, 2]
- push: 向数组的末尾添加一个或更多元素,并返回新的长度。 [1]
arr1.push(5); // 4, arr1为[1, 2, 3, 5]
- shift: 与pop功能相似,这个方法时删除并返回第一个元素 [1]
arr1.shift(); // 1, 此时arr1为[2, 3]
- unshift: 与push功能相似,向数组开头添加一个或更多元素,并返回新的长度 [1]
arr1.unshift(0); // 返回4,此时arr1为[0, 1, 2, 3]
- reverse: 颠倒数组中元素的顺序。 [1]
arr1.reverse(); // [3, 2, 1]
- sort: 对数组进行排序,可以接受函数,设置排序的规则,默认是字典序(就是英文字典那种a排完,排b的情况) [1]
arr4.reverse(); // 先故意倒序,[9, 8, 7...]
arr4.sort(); // 对数组进行排序, [1, 2, 3...]
...
[11, 101].sort(); // [101, 11], 字典序中,0在1前,所以结果是这样
...
// sort可以接受一个参数来决定其排序顺序
/* 比如前一个数比后一个数小
* 函数接收两个参数,就是表示数组中要比对元素,如果该函数的返回值大于0,表示第一个成员排在第二个
* 成员后面;其他情况下,都是第一个元素排在第二个元素前面
*/
[10111, 1101, 111].sort(function (a, b) {
return a - b;
}); // [111, 1101, 10111]
- slice: 返回指定范围的数组元素(可以接受两个参数,开始位置start和结束位置end,包含开始位置的元素,不包含结束位置的元素,下标从0开始算) [0]
slice(start, end);
arr4.slice(0, 4); // [1, 2, 3, 4]
- splice: 向/从数组中添加/删除/替换项目,然后返回被删除的项目 [1]
splice(index,howmany,item1, item2...);
// howmany设为0就不会删除元素,该方法变成向指定位置添加元素
arr4.splice(1, 0, 'a'); // 没有元素删除,此时返回为[]
// 此时arr4为[1, "a", 2, 3, 4, 5, 6, 7, 8, 9]
...
// howmany设为1就会删除元素,不过此时方法看起来是替换元素
arr4.splice(1, 1, 'b'); // 返回["a"],这是被删除的元素
// 此时arr4为[1, "b", 2, 3, 4, 5, 6, 7, 8, 9]
...
// howmany设为2以上的数字,就能体现出删除元素的功能了
arr4.splice(1, 2, 'b'); // ["b", 2]
// 此时arr4为[1, "b", 3, 4, 5, 6, 7, 8, 9]
- map: 对数组的进行迭代,然后把每一次的执行结果组成一个新数组返回 [0]
// isEven参数x接收了arr5每个元素,并进行判断,并把判断结果返回成一个数组
arr5.map(isEven);
// 返回新数组[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]
...
arr5.map(isEven2); // 如果不设置return值,运行结束后的结果会是undefined组成的数组
...
// map方法的函数可传入三个参数:当前成员、当前位置和数组本身。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
}); // [0, 2, 6]
// map方法还可以接受第二个参数,用来指定在什么数组中运行(相当于是调用call方法)
let arr = ['a', 'b', 'c'];
[1, 2].map(function (e) {
return this[e];
}, arr); // ["b", "c"]
- forEach: 与map方法类似(函数方法同样可以接受三个参数以及第二个参数),对数组进行迭代,区别在于不会返回新的数组 [0]
arr6.forEach(isEven); // 没有任何返回
arr6.forEach(isEven2); // 在控制台会输出每一个元素判断后的值
...
let out = [];
[1, 2, 3].forEach(function(elem) {
this.push(elem * elem);
}, out); // [1, 4, 9] forEach的方法不会进行阻断会一直执行到结束
- filter: 对数组进行迭代(函数方法同样可以接受三个参数以及第二个参数),把符合条件的元素,组合成一个新数组 [0]
arr6.filter(isEven); // [2, 2]
arr6.filter(isEven2); // [] 如果没有使用return,最终是个空数组
...
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
}); // [1, 3, 5]
- some: 对数组进行迭代(函数方法同样可以接受三个参数以及第二个参数),测试是否至少有一个元素通过由提供的函数实现的测试,成功就会返回true [0]
arr6.some(isEven); // true,数组中存在偶数
- every: 对数组进行迭代(函数方法同样可以接受三个参数以及第二个参数),要求每个数组元素都要符合条件,才会返回true [0]
arr6.every(isEven); // false,数组中有奇数,不符合每个元素都是偶数的条件
- reduce: 对数组元素进行迭代,数组元素按顺序进行两两操作,并把结果继续传递当作下次计算的第一个元素,直至遍历到最后一个数组元素,并返回最后计算结果 [0]
reduce(累积变量, 当前变量, 当前位置, 原数组);
累积变量默认是数组第一个元素
arr2.reducce(function(a, b) {
console.log(a, b);
return a * b;
});
...
// 这时控制台输出为,20就是数组元素4,5的计算结果,最后返回的是20和6的计算结果
// 计算规则可以随意定位,并不局限于四则运算
4 5
20 6
< 120
...
arr2.reduce(function (a, b) {
return a + '-' + b;
});
// 这是控制台输出'4-5-6'
- reduceRight: 规则和reduce一致,区别在于reduce是按数组顺序进行计算,reduceRight是按数组逆序进行计算 [0]
累积变量默认是数组最后一个元素
arr2.reduceRight(function (a, b) {
return a + '-' + b;
});
// '6-5-4'
- indexOf: 数组迭代,查找数组中是否有指定元素,有的话返回出现的位置(数组下标,从0开始),同时终止迭代,否则遍历完整个数组,如果整个数组都没有指定数组,该值返回-1 [0]
indexOf内部是事业严格相等进行判断,所以数组元素为NaN的话会判断出错
arr2.indexOf(6); // 2
arr2.indexOf(60); // -1
...
arr2.indexOf(5, 2); // -1,接受第二个参数表示从什么位置开始搜索
arr2.indexOf(5, 1); // 1
- lastIndexOf: 和indexOf方法类似,区别在于这个方法是查询元素最后出现的位置 [0]
arr6.indexOf(1); // 0
arr6.lastIndexOf(1); // 4
- copyWithin: 在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组 [1]
copyWithin(target [,start, end]);
target(必需):从该位置开始替换数据,如果为负值,表示倒数
start(可选):从该位置开始读取数据,默认为0,如果为负值,表示倒数
end(可选):到该位置前停止读取数据,默认是数组的长度,如果为负值,表示倒数
[1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5] 4, 5覆盖了1, 2
// 在开始读取第3个位置到结束数组元素,从开始位置进行覆盖
...
[1, 2, 3, 4, 5].copyWithin(0, 2); // [3, 4, 5, 4, 5] 3, 4, 5覆盖了1, 2, 3
- entries: 返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对
let arr1 = [0, 2, 4];
let entries = arr1.entries();
entries.next();
//
done: false
value: (2) [0, 0]
...
entries.next().value;
// [1, 2]
- find: 用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
arr1.find(isEven); // 2 返回第一个符合是偶数的数字
arr1.find(isEven2); // 如果方法没有使用return,则返回undefined
...
// 接收三个参数,当前值,当前的位置,以及原数组
[1, 5, 10, 15].find((value, index, arr) => value > 9); // 10
// 可接收第二个参数,用来绑定回调函数的this对象(注意箭头函数的使用)
var age = 9;
[1, 5, 10, 15].find((value) => value > this.age); // 10
...
let age = 9;
[1, 5, 10, 15].find((value) => value > this.age); // undefined
...
var age = 9;
[1, 5, 10, 15].find(function f(value) {return value > this.age}, {name:'rede', age:11}); // 15
- findIndex: 用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
[1, 5, 10, 15].findIndex((value, index, arr) => value > 9); // 2
...
// find和findIndex因为可以接收回调函数时所以可以识别NaN
[NaN].indexOf(NaN); // -1
[NaN].findIndex(y => Object.is(NaN, y)); // 0
- fill: 使用给定值,填充一个数组 [1]
['a', 'b', 'c'].fill(7); // [7, 7, 7]
// 可接收第二个和第三个参数,表示指定填充的起始位置和结束位置
['a', 'b', 'c'].fill(7, 1, 2); // ['a', 7, 'c']
...
// fill的拷贝是浅拷贝
let arr1 = [1, 2, 3];
let val = {name:'rede', age:10};
arr1.fill(val);
arr1[0].age; // 10
val.age = 12;
arr1[0].age; // 12
- toString: 返回一个字符串,表示指定的数组及其元素[0]
[4, 5, 6].toString(); // "4,5,6"
- flat: 会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
扁平化嵌套数组
let arr = [1, 2, [3, 4, [5, 6]]];
arr.flat(); // [1, 2, 3, 4, Array(2)]
arr.flat(2); // [1, 2, 3, 4, 5, 6]
...
// 使用 Infinity 作为深度,展开任意深度的嵌套数组
arr.flat(Infinity); // [1, 2, 3, 4, 5, 6]
移除数组中的空项
[1, 2, , 4, 5].flat(); // [1, 2, 4, 5]
- flatMap: 使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些
let arr = [1, 2, 3, 4];
arr.map(x => [x * 2]); // [[2], [4], [6], [8]]
arr.flatMap(x => [x * 2]); // [2, 4, 6, 8]
...
// flatMap的压缩只会压缩一层,相当于flat的默认参数
arr.flatMap(x => [[x*2]]); // [[2], [4], [6], [8]]
- includes: 返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似
[1, 2, NaN].includes(NaN); // true
...
// 该方法的第二个参数表示搜索的起始位置,默认为0,如果是负值,表示倒数位置,但是如果这个负值的绝对值大于数组长度,则会重置为0,如果数组是正值不做特色处理
[1, 2, 3].includes(2, 2); // false
[1, 2, 3].includes(2, -4); // true -4的绝对值大于3,所以重置为0