数组的“增 删 查 改”
一、概述
数组,即集合,所谓集合,就是存储数据的容器。我们可以把教室理解为一个集合,而学生作为存储在集合里面的数据,叫做元素。
JavaScript集合类型主要使用数组,数组为一个特殊对象。ES6新增了Set数据结构,也用于表示一个集合。
数组是有序排列的一组值的集合,这些值被包含在一对方括号内,里面的这些值称作数组元素。每个数组内的值都会有一个键名,这些键名其实就是一个从0开始依次计 数的下标,每个数组都会有一个“length”属性,表示数组元素的个数,所以数组内元素最大的下标值为**“length - 1”**。
数组内的值可以是JavaScript允许的任何类型的值,甚至可以是数组自身,像这样数组内包含另外一个数组的数组被称作二维数组任然继续包含数组,这样的数组就会形成“多维数组”。数组的基本表现形式如下:
**var arr = [1, "a", {}, [], false];**
提示:数组虽然有很多独立的特性,但它的数据类型仍然为对象型(object)。
二、创建数组
// 1.字面量方法(优先使用)
var arr1 = [1, 2, 3];
// 2.对象构造方法
var arr2 = new Array(1, 2, 3);
// 3.数组也可以先声明后赋值
var arr3 = [];
arr3[0] = “Petter”;
arr3[1] = “Tom”;
arr3[3] = “Lily”
// [“Petter”, “Tom”, undefined x 1, “Lily”]
var arr4 = new Array();
arr4[0] = “Petter”;
arr4[1] = “Tom”;
arr4[3] = “Lily”
// [“Petter”, “Tom”, undefined x 1, “Lily”]
三、访问 & 修改数组元素 *
访问:数组名[下标]
修改:数组名[下标] = 新值
var names = [“达摩”, “张良”, “吕布”];
// 1. 访问数组元素
names[0]; // “达摩”
// 2. 修改数组元素
names[1] = “貂蝉”; // [“达摩”, “貂蝉”, “吕布”]
四、数组空位
in 运算符用于检测数组元素的某个位置是否存在元素,返回的是一个布尔值。如果为 false,则表示对应下标位置的值不存,反之亦然。
var names = [“达摩”, , “貂蝉”, “吕布”];
heros
(4) [“达摩”, empty, “貂蝉”, “吕布”]
0 in heros;
true
1 in heros;
false
2 in heros;
true
heros[1];
undefined
需要在这里进行补充说明的是,上例中下标为1的地方没有值,称作“空位(hole)”,它返回的值为 undefined,但仍然会被计入数组的长度属性 length 中。而length的值是由数组中最后一个元素的下标+1决定的,我们来看一个示例:
var heros = [“达摩”, “貂蝉”, “吕布”];
heros.length
3
heros[99] = "李白
heros.length
100
五、数组对象 *
1. 长度
通过 length 属性访问数组元素长度
var heros = [“达摩”, “貂蝉”, “吕布”];
heros.length
3
2. 数组判断
判断某个对象是否是一个数组使用 Array.isArray() 方法,如果是,则返回 true,否则返回 false。
Array.isArray("");
false
Array.isArray({});
false
Array.isArray(true);
false
Array.isArray([]);
true
3. 添加元素
添加数组元素的方法比较多,主要有以下种:
数组名[下标]:通过下标添加元素,效率高,建议使用。(改变原始数组)
push():在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度*( 改变原数组)*。
unshift():在数组的开始位置添加一个或多个元素,并返回添加新元素后的数组长度*( 改变原数组)*。
var heros = [“达摩”, “貂蝉”, “吕布”];
// 1. 数组名[下标],只能在末尾添加
heros[heros.length] = “露娜”;
heros
(4) [“达摩”, “貂蝉”, “吕布”, “露娜”]
// 2. push()
heros.push(“上官婉儿”, “嬴政”);
heros
(6) [“达摩”, “貂蝉”, “吕布”, “露娜”, “上官婉儿”, “嬴政”]
// 3. unshift()
heros.unshift(“李白”);
7
heros
(7) [“李白”, “达摩”, “貂蝉”, “吕布”, “露娜”, “上官婉儿”, “嬴政”]
4. 合并数组
apply():合并数组(只能合并两个)( 改变原数组)
concat():合并数组(允许合并多个数组) ( 不改变原数组)
[…[]]:ES6 序列化合并数组,高效简洁,建议使用。
var a = [“李白”];
var b = [“王昭君”, “虞姬”];
// 1. apply
Array.prototype.push.apply(a, b);
a
(3) [“李白”, “王昭君”, “虞姬”]
b
(2) [“王昭君”, “虞姬”]
// 2. concat
var arr = a.concat(b);
arr
(3) [“李白”, “王昭君”, “虞姬”]
// 3. […[]]
var arr = […a, …b];
arr
(3) [“李白”, “王昭君”, “虞姬”]
5. 删除元素
删除数组元素的方法如下:
pop():删除数组最后一个元素( 改变原数组)。
shift():删除数组第一个元素( 改变原数组)。
splice():范围删除( 改变原数组)*。*
var heros = [“露娜”, “上官婉儿”, “李白”, “嬴政”, “貂蝉”, “兰陵王”, “亚瑟”];
// 1. pop
heros.pop();
heros
(6) [“露娜”, “上官婉儿”, “李白”, “嬴政”, “貂蝉”, “兰陵王”]
// 2. shift
heros.shift();
heros
(5) [“上官婉儿”, “李白”, “嬴政”, “貂蝉”, “兰陵王”]
提示:以上两种方法在删除数组元素时只能一个一个删除。
删除数组元素中 splice 方法最为灵活,不仅可以范围删除数组元素,还可以实现数组元素的插入和替换。该方法语法形式为:
splice(location, count, items…)
参数解读:
location:删除的起始位置
count:删除的元素个数
items:要被插入数组的新元素。
// 1. 删除 -> 删除李白、嬴政
heros.splice(1, 2);
heros
(3) [“上官婉儿”, “貂蝉”, “兰陵王”]
// 2. 替换 -> 把兰陵王替换成鲁班七号
heros.splice(2, 1, “鲁班七号”);
heros
(3) [“上官婉儿”, “貂蝉”, “鲁班七号”]
// 3. 插入 -> 在鲁班七号之前插入吕布、赵云
heros.splice(2, 0, “吕布”, “赵云”);
heros
(5) [“上官婉儿”, “貂蝉”, “吕布”, “赵云”, “鲁班七号”]
另外,该方法的第一个参数可以为负数(但是第二个不能,因为它表示长度),表示从数组末端开始计数,开始计数的值为“-1”。
heros.splice(-1, 1);
heros
(4) [“上官婉儿”, “貂蝉”, “吕布”, “赵云”]
如果只有第一个参数,则表示从指定位置删除到末尾:
heros.splice(1);
heros
[“上官婉儿”]
友情提示:到目前位置,我们已经学了 split、slice、splice 三个方法,这三个方法特别容易混淆,所以一定要注意区分。
6. 截取元素
截取数组元素与字符串截取类似,使用 slice 方法,该方法语法形式如下:
arr.slice(startIdx, endIdx);
语法解读:
startIdx:起始位置(从下标0开始)
endIdx:结束位置,不包括结束位置的元素,如果省略该参数,则表示从头截取到末尾。
var heros = [“上官婉儿”, “李白”, “嬴政”, “貂蝉”, “兰陵王”];
var res = heros.slice(1, 4);
res
(3) [“李白”, “嬴政”, “貂蝉”]
var res = heros.slice(1);
res
(4) [“李白”, “嬴政”, “貂蝉”, “兰陵王”]
除此之外,该方法还可以利用原型链中的 call() 方法将一个类似数组转化成真正的数组,其语法形式为:
Array.prototype.slice.call(obj);
代码示例:
<ul class="list">
<li>LIST-1</li>
<li>LIST-2</li>
<li>LIST-3</li>
<li>LIST-4</li>
<li>LIST-5</li>
</ul>
// 1. 获取所有的.list下li标签
var lis = document.querySelectorAll(".list li");
console.log(lis);
/*
NodeList(5) [li, li, li, li, li]
0: li
1: li
2: li
3: li
4: li
length: 5
proto: NodeList
*/
// 2. 企图在lis对象上调用push方法
// lis.push("
// 3. 程序会报错,因为lis本质上不是数组而是NodeList对象
// Uncaught TypeError: lis.push is not a function
// 4. 如果想要调用push方法添加内容,需将类似数组转为真正的数组
lis = Array.prototype.slice.call(lis);
console.log(lis);
/*
(5) [li, li, li, li, li]
0: li
1: li
2: li
3: li
4: li
length: 5
proto: Array(0)
*/
// 5. lis已经被转换成真正的输入,所以可以调用push方法了
lis.push("
console.log(lis);
将一个类似数组转换为真正的数组的意义在于,类似数组不具有数组的方法,直接对一个类似数组使用数组的方法浏览器会报错,而很多时候我们的操作只有通过使用数组的方法才能完成,或用数组的方法完成才是最佳的选择。
#7. 数组排序
在数组中排序使用 sort() 方法。
7.1. 数字排序
sort() 方法对数组成员进行升序排序,默认是按照ASC II 码顺序排序的,排序后,原数组将被改变。
var arr = [1, “b”, 3, 2, “A”, “a”, “B”];
arr.sort();
arr
(7) [1, 2, 3, “A”, “B”, “a”, “b”]
sort() 的排序原理是根据字符一一对应进行排序,首先会比较第1个字符,如果第1个字符相等,则继续比较第2个字符,以此类推….
var arr = [1, 12, 7, 5];
arr.sort();
arr
(4) [1, 12, 5, 7]
这样一来,就会导致排序不精确。若要对这样的数组实现数值大小的排序(实际项目中更多地是需要这样的结果),可以通过给 sort() 方法配置一个函数参数来实现,函数中需要配置两个参数,使该方法能实现一个差值排序的算法。用第一个参数减去第二个参数,得到的是一个升序数组;用第二个参数减去第一个参数,得到的是一个降序的数组。如例:
var nums = [1, 8, 13, 30];
// 1、升序
var ascending = nums.sort(function(a1, a2) {
return a1 - a2;
});
console.log(ascending); // [1, 8, 13, 30]
// 2、降序
var descending = nums.sort(function(a1, a2) {
return a2 - a1;
})
console.log(descending); // [30, 13, 8, 1]
通过示例可以发现,得出的排序结果已经不再是按照ASC II中首字符的前后顺序来排序了,而是按照数学中数字的大小来进行的排序。而且当 sort() 方法内的函数的两个参数在执行差值运算的位置发生变化后,得出结果的排序升降关系也不一样。
7. 2.对象排序
那么如果数组里面的元素是对象类型呢?我们又该如何处理,我们俩看一个示例:
var stus = [
{stuNum: 1101, name: ‘曹操’, score: 95},
{stuNum: 1102, name: ‘吕布’, score: 50},
{stuNum: 1103, name: ‘貂蝉’, score: 65},
{stuNum: 1104, name: ‘赵云’, score: 70}
];
var sortArr = stus.sort();
console.log(sortArr);
/*
(4) [{…}, {…}, {…}, {…}]
0: {stuNum: 1101, name: “曹操”, score: 95}
1: {stuNum: 1102, name: “吕布”, score: 50}
2: {stuNum: 1103, name: “貂蝉”, score: 65}
3: {stuNum: 1104, name: “赵云”, score: 70}
length: 4
proto: Array(0)
*/
通过访问遍历数组元素可以发现,数组内这四个对象的位置完全没有发生改变。其实要实现这个需求也是有办法的,就是同样需要给“sort()”方法配置一个函数作为参数来实现,实现思路和对数组排序时配置函数一致。
var sortArr = stus.sort(function(obj1, obj2) {
return obj2.score - obj1.score;
});
console.log(sortArr);
/*
(4) [{…}, {…}, {…}, {…}]
0: {stuNum: 1101, name: “曹操”, score: 95}
1: {stuNum: 1104, name: “赵云”, score: 70}
2: {stuNum: 1103, name: “貂蝉”, score: 65}
3: {stuNum: 1102, name: “吕布”, score: 50}
length: 4
proto: Array(0)
*/
7.3.字符串排序
var strs = [“Apple”, “Banana”, “Angular”, “Orange”];
strs.sort(function(a, b) {
return a.localeCompare(b);
});
strs
(4) [“Angular”, “Apple”, “Banana”, “Orange”]
7.4.中文排序
var citys = [“上海”,“北京”,“杭州”, “成都”];
citys.sort(function(a, b) {
return a.localeCompare(b, “zh-CN”);
});
citys
(4) [“北京”, “成都”, “杭州”, “上海”]
8. 数组倒序
reverse() 方法的作用是将已有数组倒序排列,并返回改变后的数组。该方法同样会改变原数组。
var arr = [1, 2, 3, 4];
arr
arr.reverse();
(4) [4, 3, 2, 1]
需要注意的是该方法不具备 sort() 方法那样对数组排序的能力,它只是单纯地将当前的数组的元素进行先后顺序地调转而已。所以,在对一个有序(已经完成升序或降序排序)的数组进行反向排序的时候,会比 sort() 方法通过配置函数的参数,再对函数的参数进行位置调整来计算差值这种方式的性能要高很多。
9. 遍历修改数组元素
map 方法对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组。该方法不会改变原来的数组。语法形式:
arr.map(function(item, index, arr) {})
参数解读:
item:每一个数组元素
index:数组元素对应的下标,可选参数
arr:原始数组,可选参数
提示:函数参数的名字是可以随意命名的。
var heros = [“阿珂”, “李白”, “貂蝉”];
heros.map(function(item, index, arr) {
console.log(item:${item}, index:${index}, arr: ${arr}
);
});
/*
VM1609:2 item:阿珂, index:0, arr: 阿珂,李白,貂蝉
VM1609:2 item:李白, index:1, arr: 阿珂,李白,貂蝉
VM1609:2 item:貂蝉, index:2, arr: 阿珂,李白,貂蝉
*/
map方法可以操作/修改数组元素。比如有一个数组存储了商品的价格,现在通过map方法给每个数字前面加上一个 ¥ 符号。
prices.map(function(price, index, arr) {
return ¥${price}
;
});
(4) [“¥66”, “¥99”, “¥85.5”, “¥32”]
10. 数组遍历
数组遍历就是依次获取数组的每一个元素进行相应的操作,js提供了很多遍历数组元素的方法,如下所示:
方法 说明
for 通过for循环遍历数组元素,可以把计数器 i 当成数组元素的下标来访问
for-in 遍历的是下标
for-of 直接遍历数据
forEach forEach是数组对象提供的方法,类似于map方法,唯一的区别在于forEach没有返回值,只是单纯的遍历数据。
示例参考:
var heros = [“阿珂”, “李白”, “貂蝉”];
// 1. for
for(var i = 0, len = heros.length; i < len; i++) {
console.log(heros[i]);
}
// 2. for-in
for(var index in heros) {
console.log(heros[index]);
}
// 3. for-of
for(var hero of heros) {
console.log(hero);
}
// 4. forEach
heros.forEach(function(hero, index, arr) {
console.log(hero, index, arr);
});
VM2543:2 阿珂 0 (3) [“阿珂”, “李白”, “貂蝉”]
VM2543:2 李白 1 (3) [“阿珂”, “李白”, “貂蝉”]
VM2543:2 貂蝉 2 (3) [“阿珂”, “李白”, “貂蝉”]
11. 过滤元素
filter 方法的主要作用是过滤数组元素,它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。和之前的 map 和 forEach 方法一样,该方法的函数仍旧支持3个参数,参数位和之前的这两个方法也是一样的,分别表示:数组元素、元素下标和原数组。
先看一个简单的示例,过滤数组元素中的偶数:
var arr = [1, 2, 3, 4, 5];
var res = arr.filter(function(item, index, arr) {
return item % 2 == 0;
});
res
(2) [2, 4]
12. 查询数组元素
indexOf() 方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回**-1**。
lastIndexOf() 方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1。
includes() 方法用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false。
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
findIndex 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
var arr = [1, 2, 3, 2, 5];
arr.indexOf(2); // 1
arr.lastIndexOf(2); // 3
arr.includes(3); // true
var res = arr.find(function(item) {
return item > 2
});
console.log(res); // 3
var index = arr.findIndex(function(item) {
return item == 3;
});
console.log(index);
13. 数组转字符串
join() 方法可以将数组转为字符串
var name = [“张三”, “李四”];
var nameStr = name.join(","); // “张三,李四”
14. 数组元素累加
reduce 语法规则:
arr.reduce(callbackfn[, initialValue])
对数组中的所有元素调用指定的回调函数。该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。
参数 定义
arr 必需。一个数组对象。
callbackfn 必需。一个接受最多四个参数的函数。对于数组中的每个元素,都会调用一次 callbackfn 函数一次。不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
initialValue 可选。如果指定 initialValue,则它将用作初始值来启动累积。第一次调用 callbackfn 函数会将此值作为参数而非数组值提供。
当满足下列任一条件时,将引发 TypeError 异常:
callbackfn 参数不是函数对象。
数组不包含元素,且未提供 initialValue。
提示:
1、如果提供了 initialValue,则 reduce 方法会对数组中的每个元素调用一次 callbackfn 函数(按升序索引顺序)。如果未提供 initialValue,则 reduce 方法会对从第二个元素开始的每个元素调用 callbackfn 函数。
2、回调函数的返回值在下一次调用回调函数时作为 previousValue 参数提供。最后一次调用回调函数获得的返回值为 reduce 方法的返回值。
3、不为数组中缺少的元素调用该回调函数。
回调函数的语法如下所示:
function callbackfn(previousValue, currentValue, currentIndex, array)
可使用最多四个参数来声明回调函数。
下表列出了回调函数参数。
回调参数 定义
previousValue 通过上一次调用回调函数获得的值。如果向 reduce 方法提供 initialValue,则在首次调用函数时,previousValue 为 initialValue。
currentValue 当前数组元素的值。
currentIndex 当前数组元素的数字索引。
array 包含该元素的数组对象。
14.1. 实例解析 initialValue 参数
先看第一个例子
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
console.log(arr, sum);
打印结果:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
这里可以看出,上面的例子index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。
再看第二个例子:
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0) //注意这里设置了初始值
console.log(arr, sum);
打印结果:
0 1 0
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
这个例子index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次
结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。
注意:如果这个数组为空,运用reduce是什么情况?
var arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
//报错,“TypeError: Reduce of empty array with no initial value”
但是要是我们设置了初始值就不会报错,如下:
var arr = [];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0)
console.log(arr, sum); // [] 0
所以一般来说我们提供初始值通常更安全
14.2. 高级用法
1). 计算数组中每个元素出现的次数
let names = [‘Alice’, ‘Bob’, ‘Tiff’, ‘Bruce’, ‘Alice’];
let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
2). 数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
3).对象里的属性求和
var result = [
{
subject: ‘math’,
score: 10
},
{
subject: ‘chinese’,
score: 20
},
{
subject: ‘english’,
score: 30
}
];
var sum = result.reduce(function(prev, cur) {
return cur.score + prev;
}, 0);
console.log(sum) //60
六、链式使用
在数组的方法中,除了可以实现数组操作方法的嵌套,若所用方法返回的仍旧是一个数组的话,还可以使用方法链来完成一个特定的功能。
var arr = [1, 2, 3, 5, 4];
var res = arr.sort().reverse().map(function(item){
return item * 2;
});
console.log(res);// [ 10, 8, 6, 4, 2 ]
七、拓展
1、数组去重
// 1.
var nums = [1, 2, 3, 1, 3, 4, 5, 4];
var tmpArr = [];
for(var i = 0; i < nums.length; i++) {
var index = tmpArr.indexOf(nums[i]);
if(index == -1) {
tmpArr.push(nums[i]);
}
}
console.log(tmpArr);
// 2.
var nums = [1, 2, 3, 1, 3, 4, 5, 4];
var resArr = […new Set(nums)];
console.log(resArr);
// 3.
var nums = [1, 2, 13, 1, 13, 4, 5, 4];
var tmpArr = [];
var resArr = [];
for(var i = 0; i < nums.length; i++) {
if(tmpArr[nums[i]] != 1) {
resArr.push(nums[i]);
}
tmpArr[nums[i]] = 1;
}
console.log(tmpArr);
console.log(resArr);
// 4.
Array.prototype.removeDuplicate = function () {
var json = {};
var arr = [];
for(var i = 0, len = this.length; i < len; i++) {
if(!json[this[i]]) {
json[this[i]] = true;
arr.push(this[i]);
}
}
return arr;
}
var result = [1, 2, 3, 3, 4, 5].removeDuplicate();
console.log(result); // [1, 2, 3, 4, 5]