一、数组的基本概念
1.基本概念
- 数组是值的有序集合,每个值叫做一个元素,而每个元素在数组中有一个位置,以数字表示,被称为索引
- 数组是无类型的,也就是说数组元素可以是任意类型,并且同一个数组中的不同元素也可能有不同的类型,这也是JavaScript弱类型的特性
- 数组的元素可以是对象或其他数组,因此可以创建出更复杂的数据结构
- 数组是动态的,可以根据需要增长或者缩减,并且在创建数组时无需声明一个固定的大小或者在数组大小发生变化时无需重新分配空间
- 数组可以是稀疏的
- 数组元素的索引不一定要连续的,他们之间可以有空缺
- 每个JavaScript数组都有一个length属性,表示该数组的长度
- 针对非稀疏数组,该属性就是数组元素的个数,而非稀疏数组,length比实际元素个数要大
- JavaScript数组是JavaScript对象的特殊形式,也就是说数组也是一个对象,数组的索引可以认为是整数的属性的
- 数组继承自Array.prototype中的属性,数组的原型就是Array
- 它定义了一套丰富的数组操作方法,不仅对真正的数组有效,而且对“类数组对象”同样有效,比如字符串、arguments、DOM里的nodeList等等
2.创建数组
数组也是一个对象,因此数组也是一个引用类型,有几种基本的方式可以创建数组,一种是使用Array构造函数,一种是字面量的形式,比如
{
let arr = new Array(1,2,3,4);
let array = Array("red","blue"); // 也可以省略new关键字
let arr1 = new Array(5) // 表示创建一个长度为5的空数组
let arr2 = [1,2,3,4]; // 跟上面一样
console.log(arr,arr1);
console.table(arr); // 可以使用table用表格的方式在控制台显示
}
参数可以是需要创建的元素,也可以是一个数字,表示要创建数组元素的个数。表格的方式也会更加清晰的看到数组的索引与值,字面量形式创建的数组与构造函数的方式是一样的,但是每个数组是保存在不同的内存地址,虽然元素一样。
数组空位
使用字面量形式创建数组时,可以使用一串逗号来创建空位,JS会将逗号之间相应索引位置当成空位,在ES6中空位的值是undefined
{
const arr = [,,,];
console.log(arr.length); // 长度为3
console.log(arr); // 3个空元素的数组
const arr1 = [1,2,,,]
for(let o of arr1){
console.log(o === undefined) // false false true true
}
}
在实际开发中,为了避免一些不一致的行为和隐患,因此避免使用空位,而直接用undefined代替
二、数组的操作与使用
1.读写数组元素
使用[]操作符可以用来访问或者更改数组的元素,方括号里可以是任意的返回非负整数值的表达式
{
let array = ["red","blue",[1,2]];
let arr = [{name:"Tom",age:20},{name:"Jack",age:19}]
// 获取元素
console.log(array[2][1]); // 2,访问索引为2的元素的第二个元素
console.log(arr[0].name); // 输出Tom
}
{
// 数组元素的修改
let array = ["red","yellow","blue"];
array[2] = "green"; // 修改第三个元素
array[3] = "black"; // 增加第四个元素
// const关键字
const arr = [1,2,3];
arr[2] = 100;
console.log(arr); // [1,2,100];
}
[]运算符既可以用来修改元素,也可以增加一个元素,使用const关键字定义的是常量,是不能修改其内存地址的,因此普通类型的常量就不能修改,但是数组是引用类型,修改的是它的引用,本身的地址没有改变,因此可以修改。
数组中元素的数量是保存在length属性当中的,这个属性始终返回0或者大于0的值。它的特点是,不是只读的,因此我们可以修改length的值,就可以用来删除或添加元素
{
let arr = [1,2,3];
let arr1 = [];
console.log(arr.length,arr1.length); // 输出3,0
// 通过length属性更改元素
arr.length = 2;
console.log(arr[2]); // 返回undefined
arr.length = 4;
console.log(arr[3]) // 值为undefined,会用undefined填充,也就是空位元素
// 可以使用Object.defineProperty让数组的length属性变成只读的
Object.defineProperty(arr,"length",{
writeable:true,
configurable:false,
});
}
使用数组的方法添加删除数组元素
pop():表示删除数组最后一个元素
shift():表示删除数组第一个元素
push():表示在数组最后增加一个元素
unshift():表示在数组的第一个位置增加一个元素
splice():从数组中添加或删除元素
{
let arr = [1,2,3,4];
arr[arr.length] = 5; // 相当于在数组的最后一个位置增加元素5
arr.push(6); // push方法在数组的最后添加一个元素
arr.unshift(0); // unshift方法在数组的第一个位置添加一个元素
console.log(arr); // [0, 1, 2, 3, 4, 5, 6]
delete arr[1] // 删除指定位置的元素
arr.pop(); // 删除数组最后一个元素
arr.shift(); // 删除数组的第一个元素
console.log(arr); // [ <1 empty item>, 2, 3, 4, 5 ]
// splice方法用于对数组元素进行插入、删除、更改的操作
arr.splice(1,2,...[22,33]); // 在索引为1的位置,操作两个元素,用扩展运算符把后面的数组展开替换之前的元素
console.log(arr); // [ <1 empty item>, 22, 33, 4, 5 ]
arr.splice(1,0,...[222,333]) //在索引为1的位置,操作0个元素,插入两个元素
console.log(arr); // [ <1 empty item>, 222, 333, 22, 33, 4, 5 ]
arr.splice(1,2); // 在索引为1的位置,操作2个元素,没有第三个参数表示删除两个元素
console.log(arr); // [ <1 empty item>, 22, 33, 4, 5 ]
}
通过splice方法可以考虑在数组原型上为页面中所有数组扩展出插入、删除和替换元素的方法。
{
// 在数组原型上扩展删除方法
Array.prototype.remove = function(index,len){
this.splice(index,len);
return this; // 返回修改后的数组
}
// 在数组原型上扩展插入方法,arr为要插入的数据数组
Array.prototype.insert = function(index,arr){
this.splice(index,0,...arr);
return this;
}
// 在数组原型上扩展替换方法,arr为要替换的数据数组
Array.prototype.replace = function(index,arr){
this.splice(index,arr.length,...arr);
return this;
}
const arr = [1,2,3,4,5];
// 在索引为1的位置,删除2个元素
arr.remove(1,2);
console.log(arr); // [1,4,5]
// 在索引为1的位置,插入一个数组,有两个元素
arr.insert(1,["Tom",20])
console.log(arr); // [1,"Tom",20,4,5]
// 在索引为1的位置,替换两个元素
arr.replace(1,["Jack",18])
console.log(arr); //[ 1, 'Jack', 18, 4, 5 ]
}
合并数组与展开语法(扩展运算符)
{
// es5的方式
let arr = ["red","blue"];
let person = ["Tom","Jack"];
// 通过循环获得数组的每一个值
for(const value of person){
arr.push(value); // 追加到arr数组里
}
console.log(arr); // [ 'red', 'blue', 'Tom', 'Jack' ]
// 数组的concat方法
arr = arr.concat(person);
console.log(arr); // [ 'red', 'blue', 'Tom', 'Jack' ]
// es6的方式
let array = [1,2];
let array2 = [3,4];
array = [...array,...array2];
console.log(array); // [ 1, 2, 3, 4 ]
}
在es6中有了扩展运算符,这样就使得很多操作方便了许多,在后面还可以运用在函数参数里,来获取所有剩余的参数
数组的转换
所有对象都有toString和valueOf方法,数组也是,其中valueOf返回的还是数组本身,而toString返回由数组中每个值的字符串形式拼接而成的一个逗号分隔的字符串,也就是说对数组的每个值都会调用其toString方法,得到最终的字符串
{
let colors = ["red","blue","yellow"];
console.log(colors.toString()); // red,blue,yellow
console.log(colors.valueOf()); // ["red", "blue", "yellow"]
console,log(colors); // ["red", "blue", "yellow"]
}
{
// 如果想使用不同的分隔符,可以用join()方法
let colors = ["red","blue","yellow"];
console.log(colors.join('-')); //red-blue-yellow
}
{
// 通过String转为字符串
let arr = String([1,2,3]);
console.log(arr,typeof arr); // 1,2,3,string
}
数组的遍历
{
// 数组遍历
let arr = [1, 2, 3, 4, 5]
console.log(Object.keys(arr));
arr.test = "hello";
console.log(Object.keys(arr)); // 遍历可枚举的属性,返回一个key值组成的数组
console.log(Object.values(arr)); // 返回key所对应的值的数组
for (let k in arr) {
console.log(k, arr[k]);
}
arr[8] = 9
for (let k in arr) {
console.log(k, arr[k]); // 跳过空位元素
}
for (let d of arr) {
console.log(d); // 不跳过空位,只会输出索引对应的值,并且打印出undefined
}
arr[7] = undefined;
for (let i = 0; i < arr.length; i++) {
if (i in arr) console.log(arr[i]); // 跳过空位,打印对应的值
}
}
2.数组的方法
reverse()与sort(),前者是将数组中的元素颠倒顺序,返回逆序的数组,后者是将数组中的元素排序并返回排序后的数组
{
let arr = [1,2,3,4,5];
console.log(arr.reverse()); // [5,4,3,2,1]
// sort()方法
let data = [2,55,33,65,1,6]
data.sort() // 按照字符编码排序
console.log(data); // [1, 2, 33, 55, 6, 65]
data.sort(function(a,b){
return a - b;
})
console.log(data); // [1, 2, 6, 33, 55, 65]
// 一个小例子
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
cart = cart.sort(function(a,b){
return b.price - a.price; // 从大到小
})
console.log(cart);
/*
[
{ name: 'iphone', price: 4999 },
{ name: 'huawei', price: 2999 },
{ name: 'xiaomi', price: 1999 }
]
*/
}
sort方法会以字母表顺序排序,因此在大多数情况下都是不合适的,可以给sort传递一个比较函数,该函数决定了它的两个参数在排好序的数组中的顺序
concat()方法:创建并返回一个新数组,它的元素包括调用concat的原始数组的元素和concat的每个参数
{
let arr = [1,2,3,4]
let a1 = arr.concat(10, 20);
let a2 = arr.concat([10, 20]);
let a3 = arr.concat([10, 20], [30, 40]);
let a4 = arr.concat([10, [20, 30]]);
console.log(a1); // [ 1, 2, 3, 4, 10, 20 ]
console.log(a2); // [ 1, 2, 3, 4, 10, 20 ]
console.log(a3); // [1, 2, 3, 4, 10, 20, 30, 40]
console.log(a4); // [ 1, 2, 3, 4, 10, [ 20, 30 ] ]
console.log([...arr, 10, 20]); // [ 1, 2, 3, 4, 10, 20 ]
}
slice():返回指定数组的一个片段或子数组,两个参数分别为起始位置和结束位置
splice():在数组中插入或删除元素的通用方法,不同于slice和concat,splice会修改调用的数组,也就是原数组
{
let arr = [1,2,3,4];
let arr_sli = arr.slice(1,2); // 从索引为1开始截取两个元素
console.log(arr_sli);
let arr_sli2 = arr.slice(1,-1); // -1表示截取到最后一个元素
console.log(arr_sli2)
}
ES5中定义了9个数组方法来遍历、映射、过滤、检测、简化和搜索数组。这些方法是很常用且重要的
- forEach()方法从头至尾遍历数组,为每个元素调用指定的函数
- map()方法将调用的数组的每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值
- filter()方法返回的数组元素是调用的数组的一个子集,传递的函数是用来逻辑判定的,该函数返回true或者false
- every()和some()方法是数组的逻辑判定,它们对数组元素应用指定的函数进行判定,返回true或者false
- reduce()和reduceRight()方法使用指定的函数将数组元素进行组合,生成单个值
- indexOf和lastIndexOf()搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1
forEach遍历方法
forEach方法只会对每一项运行传入的函数,没有返回值,本质上还是相当于for循环遍历数组。第一个参数为循环的数组的每一个元素,第二个参数为索引,第三个参数为原数组,第二和第三都是可选的。
{
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
cart.forEach((item,index,cart) => {
console.log(item,index,cart);
})
}
every和some方法
every和some方法会对数组进行逻辑判定,通过指定的函数来返回true或者false,对every来说,传入的函数必须对每一项都返回true,最后的结果才为true,否则就返回false。而对some来说,只要有一项让传入的函数返回true,它就会返回true。
{
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
// every方法
let res = cart.every((item) => {
return item.price > 2000;
})
console.log(res ? '所有商品大于2000元' : '有部分商品小于2000元'); // res为false,因此执行三元表达式后面的结果
// some方法
let result = cart.some((item) => {
return item.price > 2000;
})
console.log(result); // 结果为true
}
filter方法
filter函数会基于给定的函数来决定某一项是否应该包含在它返回的数组中,这样就可以实现筛选数据的功能,比如想要获得所有价格大于2000的商品,就可以使用filter方法。
{
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
let res = cart.filter((cart) => {
return cart['price'] > 2000;
})
console.log(res); // [ { name: 'huawei', price: 2999 }, { name: 'iphone', price: 4999 } ]
}
map方法
map方法也会返回一个数组,这个数组的每一项都是对原始数组中同样位置的元素运行传入函数而返回的结果。相当于对数组的每个元素执行一次操作再返回包含所有结果的数组。
{
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
let arr = ['red','blue','green'];
let res = arr.map((item) => {
item = `hello${item}`;
return item
})
console.log(arr); // 不会修改原数组
console.log(res); // [ 'hellored', 'helloblue', 'hellogreen' ]
cart.map((item) => {
item.total = 100;
// return 如果不希望直接修改原数组,可以返回一个新的数组。
})
console.log(cart); // 给数组的每个元素都添加了一个total属性。直接修改了原数组
}
值类型的数组元素使用遍历方法时,不会修改原数组的数据,如果是引用类型的数据,比如对象和数组,那么处理操作就会修改原数组的数据。
reduce和reduceRight方法
这两个方法都接收两个参数,第一个是对每一项运行的函数,第二个是可选的为处理的初始值。传入的函数有4个参数,分别为上一个归并值、当前项、当前项的索引和数组本身,这个函数返回的任何值都会作为下次调用函数的第一个参数,也就是归并值。reduceRight方法类似,只是方向相反
{
const cart = [
{name:"xiaomi",price:1999},
{name:"huawei",price:2999},
{name:"iphone",price:4999},
]
// 计算最贵商品
function maxPrice(goods){
return goods.reduce((pre,cur) => {
// 如果当前商品的价格大于下一个商品的价格,那么就返回当前商品
return pre.price > cur.price ? pre : cur
})
}
console.log(maxPrice(cart)); // { name: 'iphone', price: 4999 }
// 计算商品总价
function sum(goods){
return goods.reduce((total,cur) => {
return (total += cur.price)
},0)
}
console.log(sum(cart)); // 9997
}
indexOf和lastIndexOf
indexOf方法查找指定的元素,从左边开始查找,而lastIndexOf相反从右边开始查找,都可以指定第二个参数,表示从哪里位置开始查找,可以为负数表示从后面开始查找,都返回元素的索引,没有找到返回-1
{
let arr = [2,4,6,8,3,4];
console.log(arr.indexOf(4)); // 1
console.log(arr.indexOf(10)); // -1
console.log(arr.lastIndexOf(4)); // 5
console.log(arr.indexOf(4,-1)); // 5
console.log(arr.lastIndexOf(4,-3)) // 1
}
三、es6中关于数组的扩展
1.构造函数方法
Array.from():用于将两类对象转为真正的数组。还可以接收第二个可选的映射函数作为参数,这个函数可以直接增强新数组的值。
- 类似数组的对象
- 可遍历的对象
{
let lis = document.querySelectorAll('li');
console.log(lis);
let newArr = Array.from(lis,function(item){
return item.textContent;
})
console.log(newArr);
}
Array.of():用于将一组参数转换为数组,可以将arguments对象转换为数组。
{
function arr(...array){
let nums = Array.of(...array,5,6);
console.log(nums);
}
arr(1,2,3); // [1, 2, 3, 5, 6]
}
2.实例方法
ES6中新增了7个方法用于对ES5的补充
- copyWithin():会在当前数组内部将指定位置的元素复制到其他位置(会覆盖原有元素)返回当前数组
{
let arr = ['a','b','c','d','e'];
// 第一个参数从哪个位置替换,第二个参数从哪个位置开始读取,第三个截止位置
console.log(arr.copyWithin(0, 3, 4)); // [ 'd', 'b', 'c', 'd', 'e' ]
}
- fill():用于使用给定的值填充一个数组,第二个参数为从哪个位置开始,第三个参数为到哪个位置结束
{
let arr = [1,undefined,"Tom"]
let arr1 = [2,4,5,6,2]
console.log("fill-7",arr.fill(7)); // 将数组所有元素替换为7
console.log("fill-pos",arr1.fill(7,1,3)); // 只替换第1、2个元素 [2, 7, 7, 6, 2]
}
- entries()、keys()、values():用于遍历数组
{
// keys返回数组的索引
for (let index of [1, 2, 3, 4, 5].keys()) {
console.log(index); // 0 1 2 3 4
}
// values返回数组的值
for (let value of ['a', 'b', 'c'].values()) {
console.log(value); // a b c
}
// entries返回数组的索引
for (let [index, value] of ['a', 'b', 'c'].entries()) {
console.log(index, value); // 0 "a" 1 "b" 2 "c"
}
}
- includes():判断数组中是否包含给定的值,与字符串的includes方法类似,返回true或者false
{
// 是否包括1这个元素
console.log([1, 2, NaN].includes(2)); // true
// 自定义函数实现includes方法
let arr = [1,2,3,4,5];
function includes(array,value){
for(let v of array){
if(v === value) return true
}
return false
}
console.log(includes(arr,3)); // true
console.log(includes(arr,10)); // false
}
- find()和findIndex():找出第一个符合条件的数组元素(或者索引),接收三个参数:元素、索引和数组本身,find返回第一个匹配的元素,findIndex放回第一个匹配元素的索引
{
const person = [
{name:"Tom",age:20},
{name:"Jack",age:24}
]
console.log(person.find((item) => item.age > 21 )) // {name: "Jack", age: 24}
console.log(person.findIndex((item) => item.age > 21 )) // 1
// 自定义函数实现find方法
function find(array,callback){
for(let v of array){
if(callback(v)) return v
}
return undefined;
}
let arr = [1,2,3,4,5]
console.log(find(arr,function(item){
return item == 100 // 没有返回undefined
}))
}