分享

JavaScript中数组常用方法汇总(ES5)

 wenxuefeng360 2022-11-05 发布于四川

1、Array.prototype.push()
在数组的后面添加一到多个元素,并返回数组长度

const arr = [1, 2]
console.log(arr.push(3)) // 3
console.log(arr) // [1, 2, 3]

2、Array.prototype.unshift()
将一个或多个元素添加到数组的开头,并返回新数组的长度

const arr = [1, 2]
console.log(arr.unshift(3)) // 3
console.log(arr) // [3, 1, 2]

3、Array.prototype.pop()
删除数组最后一个元素,并将该元素返回

const arr = [1, 2, 3]
console.log(arr.pop()) // 3
console.log(arr) // [1, 2]

4、Array.prototype.shift()
删除数组第一个元素,并将该元素返回

const arr = [1, 2, 3]
console.log(arr.shift()) // 1
console.log(arr) // [2, 3]

5、Array.isArray()
判断一个对象是不是数组,返回布尔值。

6、Array.prototype.concat()
将多个数组拼接成一个数组,返回新的数组。

const arr1 = [1, 2, 3]
const arr2 = [4, 5]
const newArr = arr1.concat(arr2)
console.log(newArr) // [1, 2, 3, 4, 5]

注:利用concat方法可以实现数组对象的拷贝,例如

const arr = [1,2,3,4,5]
const newArr = Array.prototype.concat(arr) // [1,2,3,4,5]
newArr === arr // false

7、Array.prototype.join()
将数组转换为字符串,参数设置字符串间隔,默认为逗号分隔符。

conat arr = [1, 2, 3]
console.log(arr.join()) // "1,2,3"
console.log(arr.join(:)) // "1:2:3"
console.log(arr.join(-)) // "1-2-3"
arr.toString() // "1,2,3,4"

注:利用join() 方法可以实现重复字符串,例如:

const str = new Array(4).join('a')
console.log(str) // "aaa"

8、Array.prototype.splice()
更新数组,可以实现对原数组元素的增删改。
语法:

arr.splice(index, number, item1, …, itemX)

  • [index] 必传项,整数,规定添加/删除项目的位置,负数表示从数组结尾处规定位置
  • [number] 必传项,要删除的项目数量。如果设置为 0,则不会删除项目
  • [item1, …, itemX] 可选。向数组添加的新项目。
let arr1 = [1, 2, 3, 4, 5]
arr1.slice(2, 0, 'haha')
console.log(arr1) // [1, 2, 'haha', 3, 4, 5] 新增一个元素
let arr2 = [1, 2, 3, 4, 5]
arr2.splice(2, 3)
console.log(arr2) // [1, 2] 删除三个元素
let arr3 = [1, 2, 3, 4, 5]
arr3.splice(2, 1, 'haha')
console.log(arr3) // [1, 2, 'haha', 4, 5] 替换一个元素

9、Array.prototype.sort()
Array.prototype.sort()方法用于数组元素的排序,但是往往排序结果可能让你大吃一惊:

// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];

// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']

// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]

第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。
第三个排序结果是什么鬼?简单的数字排序都能错?
这是因为Array的sort()方法默认把所有元素先转换为String再按照ASCⅡ码的顺序排序,结果’10’排在了’2’的前面,因为字符’1’比字符’2’的ASCⅡ码小。
但是sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

  • 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
  • 若 a 等于 b,则返回 0。
  • 若 a 大于 b,则返回一个大于 0 的值。

因此要按数字大小排序可以这样写:

var arr = [10,20,1,2];
arr.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
});
console.log(arr); // [1, 2, 10, 20]

10、Array.prototype.slice()
slice() 方法可从已有的数组中返回选定的元素,该方法不会修改原数组,只会返回一个新数组。

arr.slice(start, end)

  • start 可选。规定从何处开始选取,不选默认为零0。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2指倒数第二个元素,以此类推。
  • end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

返回一个新的数组,包含从 start 到 end (不包括该元素)的数组array中的元素。
示例:

let arr = [1, 2, 3, 4, 5]
console.log(arr.slice(0)) // [1, 2, 3, 4, 5]
console.log(arr.slice(2)) // [3, 4, 5]
console.log(arr.slice(1, 2)) // [2]

11、Array.prototype.reverse()
颠倒数组中元素的顺序,改变原数组。

// Base
let a = [1, 2, 3, 4, 5]
let result = a.reverse()
console.log(result)   // [5, 4, 3, 2, 1]
console.log(a)        // [5, 4, 3, 2, 1]

// More
a = [1, [2, 3], [4, 5]]
result = a.reverse()
console.log(result)   // [[4, 5], [2, 3], 1]
console.log(a)        // [[4, 5], [2, 3], 1]
// 可以看到这里的反转只是基于数组的第一层,属于浅反转。

// 一个简单的深反转,使用递归实现
const deepReverse = (array) => {
  let temp = array.reverse()
  temp.forEach(v => {
    if(Object.prototype.toString.call(v) === '[object Array]') {
      deepReverse(v)
    }
  })
  return temp
}
a = [1, [2, 3], [4, 5]]
result = deepReverse(a)
console.log(result)    // [[5, 4], [3, 2], 1]

12、两个归并方法:reduce()、reduceRight()
这两个方法都会迭代数组中的所有项,然后生成一个最终返回值。他们都接收两个参数,第一个参数是每一项调用的函数,函数接受四个参数分别是初始值,当前值,索引值,和当前数组,函数需要返回一个值,这个值会在下一次迭代中作为初始值。第二个参数是迭代初始值,参数可选,如果缺省,初始值为数组第一项,从数组第一个项开始叠加,缺省参数要比正常传值少一次运算。

  • total :必需。初始值, 或者计算结束后的返回值。
  • cur :必需。当前元素。
  • index :可选。当前元素的索引。
  • arr:可选。当前元素所属的数组对象。
  • initialValue:可选。传递给函数的初始值。

arr.reduce((total , cur , index , arr) => {}, initialValue)从数组的第一项开始,逐个遍历到最后
arr.reduceRight((total , cur , index , arr) => {}, initialValue) 从数组的最后一项开始,向前遍历到第一项

示例:

// 数组求和
var arr = [1,2,3,4,5];
var result1 = arr.reduce(function(total,cur,index,arr) {
    console.log("total:"+total+",cur:"+cur+",index:"+index);
	return total+cur;
});
console.log("结果:"+result1);
// 输出
// total:1,cur:2,index:1
// total:3,cur:3,index:2
// total:6,cur:4,index:3
// total:10,cur:5,index:4
// 结果:15
var result2 = arr.reduce(function(total,cur,index,arr){	
	console.log("total:"+total+",cur:"+cur+",index:"+index);
	return total+cur;
},10);
console.log("结果:"+result2);
// 输出
// total:10,cur:1,index:0
// total:11,cur:2,index:1
// total:13,cur:3,index:2
// total:16,cur:4,index:3
// total:20,cur:5,index:4
// 结果:25

// 数组扁平化
var flattened = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b) {
    return a.concat(b);
}); 
console.log(flattened) // [0, 1, 2, 3, 4, 5]

从上面代码我们可以看出,当我们不给函数传递迭代初始值时初始值 total 为数组第一项,函数从数组第二项开始迭代;若我们给函数传递迭代初始值,则函数从数组第一项开始迭代。

13、两个索引方法indexOf()和 lastIndexOf()
两个方法都返回要查找的项在数组中首次出现的位置,在没找到的情况下返回-1

Array.indexOf(item,start) (从数组的开头(位置 0)开始向后查找)
item: 必须。查找的元素。
start:可选的整数参数。规定在数组中开始检索的位置。如省略该参数,则将从array[0]开始检索。

Array.lastIndexOf(item,start) (从数组的末尾开始向前查找)
item: 必须。查找的元素。
start:可选的整数参数。规定在数组中开始检索的位置。如省略该参数,则将从 array[array.length-1]开始检索。

const arr = [2, 4, 1, 9, 1, 2]
console.log(arr.indexOf(2)) // 0
console.log(arr.lastIndexOf(1)) // 1
console.log(arr.indexOf(3)) // -1

14、五个迭代方法:forEach()、map()、filter()、some()、every()
forEach():对数组进行遍历循环,这个方法没有返回值。jquery()也提供了相应的方法each()方法。
语法:

array.forEach(function(currentValue , index , arr){//do something}, thisValue)

  • currentValue : 必需。当前元素
  • index: 可选。当前元素的索引值。
  • arr : 可选。当前元素所属的数组对象。
  • thisValue: 可选,指定函数执行上下文。如果这个参数为空, “undefined” 会传递给 “this” 值

示例:

var Arr = [1,4,7,10];
Arr.forEach(function(currentValue, index, arr){
	console.log(index+"--"+currentValue+"--"+(arr === Arr));		
})
// 0--1--true
// 1--4--true
// 2--7--true
// 3--10--true	

map():指“映射”,方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。(不会改变数组长度,和原数组长度保持一致)
语法:

array.map(function(currentValue , index , arr){//do something}, thisValue)

示例:

const arr = [1, 2, 3]
const arr1 = arr.map((currentValue) => {
	return currentValue + 1
})
console.log(arr) // [1, 2, 3]
console.log(arr1) // [2, 3, 4]

filter():“过滤”功能,方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。和filter() 方法类似,jquery中有个 grep()方法也用于数组元素过滤筛选。

array.filter(function(currentValue , index , arr){//do something}, thisValue)

filter方法实现筛选排除掉所有小于5的元素:

var arr = [1,4,6,8,10];
var result1 = arr.filter(function(currentValue){
	return currentValue>5;
 });
console.log(result1);  // [6, 8, 10]
var result2 = arr.filter(function(currentValue){
	return currentValue>"5";
});
console.log(result2);  // [6, 8, 10]

当我们分别设置item > 5和item > "5"时, 返回的结果是一样的,由此我们可以看出函数支持弱等于==,而不是===
every():判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。
语法:

array.every(function(currentValue , index , arr){//do something},thisValue)

var arr = [1,4,6,8,10];
var result1 = arr.every(function(currentValue){
	return currentValue< 12;
});
console.log(result1);  // true
var result2 = arr.every(function(currentValue){
	return currentValue> 1;
});
console.log(result2);  // false

some():判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
语法:

array.some(function(currentValue , index , arr){//do something}, thisValue)

var arr = [1,4,6,8,10];
var result1 = arr.some(function(currentValue){
	return currentValue> 10;
});
console.log(result1);  // false
var result2 = arr.some(function(currentValue){
	return currentValue> 5;
});
console.log(result2);  // true

注:
一、终止循环
1、every 当内部return false时跳出整个循环(return true继续遍历)

//every()当内部return false时跳出整个循环
let list = [1, 2, 3, 4, 5];
list.every((value, index) => {
    if(value > 3){
        console.log(value) // 4
        return false;
    } else {
        console.log(value) // 1 2 3
        return true;
    }
});

list.every((value, index) => {
    if(value > 3){
        console.log(value)
        return false;
    } else {
        console.log(value)// 1
        // return true;
        // 如果没有返回值true 的话,也会跳出循环
    }
}); 

2、forEach没有返回值,只针对每个元素调用func

// forEach没有返回值,只针对每个元素调用func。
let list2 = [1, 2, 3, 4, 5];
list2.forEach((value, index) => {
	if(value > 3){
		console.log(value)// 4 5
        return false;//没有返回值,ruturn false 仍向下执行
    }else{
		console.log(value)// 1 2 3
        return true;
	}
});

forEach正常情况不能终止遍历过程,可以抛出一个异常终止循环,但every和some可以轻易实现

var arr = [1,2,3,4,5,6,7];

try {
	arr.forEach((item,index) => {
    	console.log(item);
        if(item == 3){
          throw new Error('break');
        }
      });
} catch (e) {
	// console.log(e);
} finally {
    
}

// some 返回true时终端遍历
arr.some(item => {
	console.log(item); // 1 2 3
    if(item == 3){
    	return true;
    }
});

// every 返回false时终端遍历,返回true继续遍历
arr.every(item => {
	console.log(item); // 1 2 3 4
    if(item == 4){
		return false;
    }else {
    	return true;
    }
});

3、some 当内部return true时跳出整个循环

let list3 = [1, 2, 3, 4, 5];
list3.some((value, index) => {
	if(value === 3){
		return true; // 当内部return true时跳出整个循环
     }
     console.log(value) // 1 2 
});

二、跳出循环
js中对数组、对象的遍历方法众多,但并不是每个方法都可以使用break/continue跳出循环,这些方法我们经常使用却也很容易混淆,特总结如下:

var arr = ['beijing', 'shanghai', 'guangzhou', 'shenzhen', 'hangzhou', 'chengdu'];
var obj = { name: 'tom', age: 20, address: 'beijing', job: 'artist' };

//for循环可以使用break/continue跳出循环
for (let i = 0; i < arr.length; i++) {
	if (i == 1) continue;
    console.log(arr[i]);
    if (i == 3) break;
}
    
//forin可以使用break/continue跳出循环
for (const key in obj) {
	if (key == 'age') continue;
    console.log(key);
    if (key == 'address') break;
}

//forof可以使用break/continue跳出循环
for (let item of arr) {
	if (item == 'shanghai') continue;
	console.log(item);
    if (item == 'shenzhen') break;
}
    
//map不能使用break/continue跳出循环
arr.map((item, i) => {
	// if (i == 1) continue;
    console.log(item);
    // if (i == 3) break;
})
    
//forEach不能使用break/continue跳出循环,但是forEach可以使用return false终止本次执行
arr.forEach((item, i) => {
	//if(i == 1) continue;
	console.log(item);
	//if(i ==3) break;
})

三、slice方法的底层内部实现及扩展
代码底层实现:

Array.prototype.slice = function(start,end){  
//ES5 中的数组方法slice的底层内部实现
var result = new Array(); //新数组
var start = start || 0;
var end = end || this.length; 
//this指向调用的对象,用了call之后,改变this的指向,指向传进来的对象
for(var i=start; i<end; i++){
	result.push(this[i]);
}
return result;	//返回的为一个新的数组
}

拓展:
slice()方法可以将类数组转化为真数组,一般用法如下所示:

//此处obj就是类数组,通过call改变this指向,使其指向obj,slice会返回一个新的数组,就是真数组
var arr = Array.prototype.slice.call(obj)  	

除了slice()方法可以将类数组转化为真数组之外,以下几种方式也可以将类数组转化为真数组

  • 声明一个空数组,通过遍历类数组将其值重新添加到新的空数组中并返回(一般这种方式不用讲了)
  • jQuery里的toArray()方法也可以将伪数组转化为真数组,其底层实现实际上使用的是数组的slice方法
  • ES6中的扩展运算符(…)也可以将某些数据结构转为数组,扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。用法如下:
 let arr = [...obj]  //此处obj是类数组
//扩展运算符将类数组转化为真数组的条件除了类数组要具有length属性之外,还必须要有内容值存在,否则无法转化
  • ES6中的新方法from(),任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。用法如下:
let arr = Array.from(obj);  //此处obj就是类数组

四、数组遍历方法性能比较
除了前文我们提到的数组遍历方法还可以利用for-in和for-of(ES6)语句:
for-in 语句:
一般用for-in来遍历对象的属性的,不过属性需要 enumerable,才能被读取到.
for-in 循环只遍历可枚举属性。一般常用来遍历对象,包括非整数类型的名称和继承的那些原型链上面的属性也能被遍历。像 Array和 Object使用内置构造函数所创建的对象都会继承自Object.prototype和String.prototype的不可枚举属性就不能遍历了.

var obj = {
    name: 'test',
    color: 'red',
    day: 'sunday',
    number: 5
}
for (var key in obj) {
    console.log(obj[key])
}

for-of 语句
for-of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。只要是一个iterable的对象,就可以通过for-of来迭代.

var arr = [{name:'bb'},5,'test']
for (item of arr) {
    console.log(item)
}

for-of 和 for-in 的区别

  • for-in 语句以原始插入顺序迭代对象的可枚举属性。for-in会把继承链的对象属性都会遍历一遍,所以会更花时间。
  • for-of 语句只遍历可迭代对象的数据。

对比遍历速度
我创建了两个数组进行对比,为什么要这样区别呢,因为不同类型的数组在javascript内存中保存的地址格式不一样,遍历的时候编辑器会根椐数组元素的类型长度计算,比如说如果数组里面全是Number类的,循环起来会比数组里面包含Number,String,Object混合型的会快,所以创建了两个数组,一个是全undefined数组,一个是混合型数组。

// 一个是空数组
var nullarr = new Array(10000) // [undefined,undefined,...undefined]

// 另一个带不同类型的数据的数组
var dataarr = []
for(var i = 0; i < 10000; i++){
    if (i % 2 ===0) {
        dataarr[i] = i.toString()
    } else {
        dataarr[i = i
    }
}
dataarr // [1,'2',3...,10000]

测试后发现有点奇怪直接检索空数组还是会比数据数组慢这是为什么呢奇怪?为了对比循环的一致性我只选其中带数据的数组dataarr进行测试。

那我们对比一下 for for len forEach for-in for-of map filter 循环的速度
在这里插入图片描述
可以看到 for循环的速度是最快的,是最老的循环,也是优化得最好的,其次是for-of这个是es6才新增的循环非常好用,最慢是for-in我们可以作一下速度排序。

for > for-of > forEach > filter > map > for-in

这很明显处理大量循环数据的时候还是要使用古老for循环效率最好,但也不是不使用for-in,其实很多时候都要根据实际应该场景的,for-in更多使用在遍历对象属性上面,for-in在遍历的过程中还会遍历继承链,所以这就是它效率比较慢的原因,比如map 速率不高,不过处理在Es6实现数组功能上面非常好用方便,轻松影射创建新数组.或者例如使用Iterator属性也是行的,所以每个循环都有合适使用的地方。

every 和 some 不完全属于数组操作方法
every 和 some 都是判断条件直接返回整个数组Boolean类型的方法.every速度会比some快很多。

在这里插入图片描述

五、最后
一张图展示JavaScript数组方法:
在这里插入图片描述

参考资料:
JavaScript中数组的常用方法(含ES6)
JS 数组循环遍历方法的对比

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多