JS数组常用操作及队列的实现

声明

1
2
3
4
var a=new Array();
var a=new Array(5);
var a=new Array([5]);
var a=[5];

判断是否为数组

1
Array.isArray(target)

拼接为字符串

1
a.join("-")

数组的增删改

添加

1
2
3
4
//添加元素到数组结尾,返回新长度
a.push("123");
//添加元素到数组开始,返回新长度
a.unshift("456");

插入另一个数组

1
2
3
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
array1 = [...array1, ...array2];

插入

splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目的数组

注释:该方法会改变原始数组。

插入 在index位置插入元素

1
arr.splice(index,0,item1,item2,...);

开始插入

1
arr.unshift("begin");

结尾插入

1
arr.push("end");

替换元素

注释:该方法会改变原始数组。

替换 从index开始的2个元素替换为

1
a.splice(index,2,item1,item2,...);

删除元素

删除最后一个

注释:该方法会改变原始数组。

1
2
// 改变原数组
a.pop();

pop() 方法将删除 arrayObject 的最后一个元素,
把数组长度减 1,并且返回它删除的元素的值。
如果数组已经为空,则 pop() 不改变数组,并返回 undefined 值。

从索引删除

删除 从index开始删除2个元素

1
a.splice(index,2);

删除第1个元素

1
arr.splice(0,1);

删除最后2个元素

要删除数组的最后两个元素,可以使用数组的 slice() 方法或 splice() 方法。

使用 slice() 方法:

1
2
3
4
let array = [1, 2, 3, 4, 5];
//不修改原数组
let newArray = array.slice(0, array.length - 2);
console.log(newArray); // [1, 2, 3]

使用 splice() 方法:

1
2
3
4
let array = [1, 2, 3, 4, 5];
//修改原数组
array.splice(array.length - 2, 2);
console.log(array); // [1, 2, 3]

保留前3个元素

1
2
3
4
let array = [1, 2, 3, 4, 5];
//修改原数组
array.splice(3, array.length - 3);
console.log(array); // [1, 2, 3]

移除对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arr = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];

let objToRemove = { id: 2, name: 'Bob' };

let index = arr.findIndex(obj => obj.id === objToRemove.id); // 找到对象在数组中的索引
if (index !== -1) {
arr.splice(index, 1); // 从数组中删除对象
}

console.log(arr);

遍历中删除数据

一般解决:此时可以在删除下标i的时候执行 i–(使下标回退一个位置)

优化解决:可以通过倒叙遍历的方法,倒叙遍历的时候不管删了多少元素,没遍历的元素不会被跳过,比上一个方法简单

一般解决方法

1
2
3
4
5
6
7
let arr = [1,2,3];
for(let i=0; i<arr.length; i++){
if(arr[i]==2){
arr.splice(i, 1);
i--;
}
}

优化方法

1
2
3
4
5
for(let i=arr.length-1; i>=0; i--){
if(arr[i] == 2){
arr.splice(i,1);
}
}

清空数组

方式1:splice函数

1
arrayObject.splice(index,howmany,element1,.....,elementX)
  • index:必选,规定从何处添加/删除元素。

  • howmany:必选,规定应该删除多少元素。未规定此参数,则删除从 index 开始到原数组结尾的所有元素。

  • element1:可选,规定要添加到数组的新元素。

示例:

1
2
var arr = [1,2,3,4];  
arr.splice(0,arr.length);

方式2:直接赋予新数组 []

1
2
var arr = [1,2,3,4];  
arr = [];

这种方式为将arr重新复制为空数组,之前的数组如果没有被引用,将等待垃圾回收。

方式3:给数组的length赋值为0

1
2
var arr = [1,2,3,4];  
arr.length = 0;

赋予数组的长度小于本身的长度,数组中后面的元素将被截断。

赋予数组的长度大于本身的长度,将扩展数组长度,多的元素为undefined。

注意

VUE的时候不要用这种方式,会导致页面不刷新。

效率比较:

多次测试发现第3种方式最快,第1种其次,第2种最慢。

排序

会修改原数组

数组反转

1
arr.reverse();

该方法会改变原来的数组,而不会创建新的数组。

单属性排序

正序:

1
arr.sort();

正序:

1
2
var arr = [40, 100, 1, 5, 25, 10];
arr.sort(function(a, b){return a - b});

逆序:

1
2
var arr = [40, 100, 1, 5, 25, 10];
arr.sort(function(a, b){return b - a});

多属性排序

根据多个属性排序

先按type正序,再按num倒序

1
2
3
4
5
6
7
arr.sort(function(a,b){
if(b.type==a.type){
return b.num-a.num
}else{
return a.type-b.type
}
})

字符串排序

1
2
3
4
5
arr.sort(
function(a,b){
return a.localeCompare(b)
}
)

如果包含中文

1
a.localeCompare(b,'zh-CN')

随机排序

1
array.sort(() => Math.random() - 0.5)

null排序

列表中的对象的属性为null的排后面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var array = [
{ name: "Mike", sex: "男" },
{ name: "John", sex: "女" },
{ name: "Alice", sex: null },
{ name: "Bob", sex: "男" },
{ name: "Emma", sex: null }
];

array.sort(function(a, b) {
let aValue = a.sex;
let bValue = b.sex;
if (aValue === null && bValue === null) {
// 如果两个对象的属性都为空,则保持原来的顺序不变
return 0;
} else if (aValue === null) {
// 如果 a 的为空,则将 a 排在 b 后面
return 1;
} else if (bValue === null) {
// 如果 b 的为空,则将 b 排在 a 后面
return -1;
} else {
// 如果两个对象的都不为空,则根据属性进行升序排序
return aValue.toString().localeCompare(bValue.toString(), "zh-CN");
}
});

console.log(array);

深拷贝与浅拷贝

深拷贝

1
2
3
const arr = [1, 2, 3, 4, 5];
const clone = JSON.parse(JSON.stringify(arr));
console.info(clone);

浅拷贝

方式1

使用展开运算符可以轻松地复制

1
2
3
const arr = [1, 2, 3, 4, 5];
const clone = [...arr];
console.info(clone);

方式2

使用数组的slice方法复制数组

1
2
3
const arr = [1, 2, 3, 4, 5];
const clone = arr.slice();
console.info(clone);

slice() 是数组对象的一个方法,用于从数组中提取指定位置的元素创建一个新的数组。

slice() 方法不会修改原始数组,而是返回一个浅拷贝(shallow copy)的新数组。

slice() 方法可以接收两个参数,即 startend。起始位置 start 是要提取的起始索引(包含在提取范围内),结束位置 end 是要提取的结束索引(不包含在提取范围内)。

如果省略 end 参数,默认会提取到数组的末尾。

合并两个数组

1
a.concat(["123"]);

concat() 方法用于连接两个或多个数组。

该方法不会改变原数组,而仅仅会返回被连接数组的一个副本。

获取数组片段

按照位置获取

不会改变原数组

1
arrayObject.slice(start,end)

参数

  • start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。

  • end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

返回值

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

获取除最后一个的其他

1
2
let arr = [1,2,3,4,5];
arr.slice(0,arr.length-1);

获取最后一个

1
2
let arr = [1,2,3,4,5];
arr.slice(arr.length-1);

随机获取2个

1
2
3
4
export function getRandomTwoItems(arr) {
const shuffledArray = [...arr]; // 复制数组,以免修改原始数组
return shuffledArray.sort(() => 0.5 - Math.random()).slice(0, 2);
}

判断是否包含

数组是否包含元素

1
2
3
4
var arr = ["a","b","2","3","c"];  
if(arr.indexOf("a")!==-1){

}

数组是否包含元素

1
2
3
4
let animals = ["a", "b", "c", "d"]

animals.includes("a") // true
animals.includes("f") // false

包含元素或属性

判断是否为数组的元素索引对象的属性

格式:(变量 in 对象)

对象为数组时,变量指的是数组的索引

对象为对象是,变量指的是对象的属性

数组示例:

1
2
3
4
5
var arr = ["a","b","2","3","str"];  
var result = ("b" in arr);
var result1 = (4 in arr);
console.info(result);
console.info(result1);

输出为:

1
2
false
true

对象示例:

1
2
3
4
5
6
7
8
9
10
11
var obj={  
w:"wen",
j:"jian",
b:"bao"
}

var result=(2 in obj);
var result1=("j" in obj);

console.info(result);
console.info(result1);

输出为:

1
2
false  
true

判断数组包含另一数组

1
2
3
4
function isArrayContained(arr1, arr2) {
// 使用 every 方法来检查 arr2 中的每个元素是否都在 arr1 中存在
return arr2.every((item) => arr1.includes(item));
}

判断数组是否包含对象

1
2
3
4
5
let animals = [{name: "dog"}, {name: "snake"}, {name: "monkey"}, {name: "donkey"}]
let aname = "monkey";

var result = animals.some(animal => animal.name === aname);
console.log(result);//true

遍历

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

arr.forEach((item) => {
console.log(item);
});

map

map()经常用来遍历数据。

map()的作用就是“映射”,也就是原数组被“映射”成对应新数组。

方法概述

map() 方法返回一个新数组,这个新数组:由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

map() 不会对空数组进行检测。

map() 不会改变原始数组。

1
2
3
4
5
6
var arr = ["a","b","c","d","e"];
arr.map(function(currentValue,index,arr){
console.log("当前元素"+currentValue)
console.log("当前索引"+index)
console.log("数组对象"+arr)
})

map的参数:

  • currentValue 必须。当前元素的值
  • index 可选。当期元素的索引值
  • arr 可选。当期元素属于的数组对象

reduce

求和

1
let totalValue = list.reduce((total, item) => { return total + item }, 0)

最大值

1
let maxValue = list.reduce((max, item) => { return Math.max(max, item) }, 0)

filter

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

注意: filter() 不会对空数组进行检测。

注意: filter() 不会改变原始数组。

1
2
3
4
5
6
7
var ages = [32, 33, 12, 40];

function checkAdult(age) {
return age >= 18;
}

var a2 = ages.filter(checkAdult);

排除数组元素的数组

1
2
3
4
const array = [1, 2, 3, 4, 5];
const exclude = [2,3];
const newArray = array.filter(item => exclude.indexOf(item)===-1);
console.log(newArray); // 输出 [1, 2, 5]

some

1
2
3
4
const array = [1, 2, 3, 4, 5];

const hasNumber = array.some(element => element===2);
console.log(haNumber); // 输出 true,因为数组中包含2

要判断一个二维数组中是否包含另一个一维数组,你可以使用嵌套的 Array.prototype.some()Array.prototype.every() 方法来实现。

首先,你可以使用 some() 方法遍历二维数组的每个子数组,然后对每个子数组使用 every() 方法来判断是否包含目标数组。

下面是一个示例:

1
2
3
4
5
6
7
8
const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const targetArray = [4, 5, 6];

const includesTarget = matrix.some(subArray =>
subArray.length === targetArray.length && subArray.every((element, index) => element === targetArray[index])
);

console.log(includesTarget); // 输出 true

在上面的示例中,includesTarget 的值将根据二维数组 matrix 是否包含目标数组 targetArray 来决定。

subArray.every() 用于检查每个子数组是否与目标数组完全匹配(即元素个数和每个元素的值都相等)。

every

判断数组中不为空的都是整数

1
2
3
4
5
6
7
let numArr = [10,8,null,10,20,100];
numArr = numArr.filter(item=>item!=null && item!="");
console.info(numArr);
let result = numArr.every(item=>{
return new RegExp("^[0-9]+$", "i").test(item+"");
});
console.info(result);

fill

1
2
3
let arr = new Array(5);
arr.fill(0);
console.info(arr);

注意填充二位数组

1
2
3
4
let dataArr = new Array(5);
dataArr.fill(new Array(8).fill(0));
dataArr = JSON.parse(JSON.stringify(dataArr));
console.info(dataArr);

注意

这里使用JSON进行了序列化和反序列化的原因是dataArr中填充的数组是同一指针的数组,修改的化会全都改变。

还用一种方式

1
let dataArr = Array.from({length: 3}, () => []);

队列的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 基于数组封装队列类
function Queue() {
// 属性
this.items = []
// 方法
// 1.enqueue():将元素加入到队列中
Queue.prototype.enqueue = element => {
this.items.push(element)
}

// 2.dequeue():从队列中删除前端元素
Queue.prototype.dequeue = () => {
return this.items.shift()
}

// 3.front():查看前端的元素
Queue.prototype.front = () => {
return this.items[0]
}

// 4.isEmpty:查看队列是否为空
Queue.prototype.isEmpty = () => {
return this.items.length === 0;
}

// 5.size():查看队列中元素的个数
Queue.prototype.size = () => {
return this.items.length
}

// 6.toString():将队列中元素以字符串形式输出
Queue.prototype.toString = () => {
let resultString = '------------队列------------\n'
for (let i of this.items) {
resultString += JSON.stringify(i) + '\n'
}
resultString += '------------队列------------\n'
return resultString
}

//清空队列
Queue.prototype.clear = () => {
this.items = [];
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 发送消息列表 接收到消息回发后移除该消息 否则超过3秒自动重发
window.msgQueue = new Queue();

function checkQueue() {
let now = parseInt(new Date().getTime() / 1000 + "");
if (window.msgQueue.size() > 0) {
let msg = window.msgQueue.front();
if (now - msg.timeunix > 3) {
if (window.ws && window.ws.readyState === 1) {
//发送消息
let timeunix = parseInt(new Date().getTime() / 1000 + "");
msg.timeunix = timeunix;
msg.b.timeunix = timeunix;
let str = JSON.stringify(msg);
let msgEnc = encStr(str);
window.ws.send(msgEnc)
if (show_log) {
console.info("↑ WS消息重发", msg);
}
}
}
}
setTimeout(function () {
checkQueue();
}, 300)
}

checkQueue();

// 接收消息时队列中移除
if (msgObj.c === 666) {
if (window.msgQueue.size() > 0) {
let msgitem = window.msgQueue.front();
if (msgitem.mid === msgObj.mid) {
logger.info("队列移除消息", msgitem)
window.msgQueue.dequeue();
}
logger.info("队列剩余消息条数", window.msgQueue.size())
}
}

这种情况会出现一个问题

如果在断网时发送消息,这个消息会在队列中,当重连时,会发送之前的消息,并且发送上线的消息,可能先收到的是上线消息的返回,但是上线消息不是在队列的顶部所以没有移除,就会出现问题。

解决方法

发消息依旧按照队列发送,但是接收消息的时候只要在数组中都移除。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 发送消息列表 接收到消息回发后移除该消息 否则超过3秒自动重发
window.msgQueue = [];

function checkQueue() {
let now = parseInt(new Date().getTime() / 1000 + "");
if (window.msgQueue.length > 0) {
let msg = window.msgQueue[0];
if (now - msg.timeunix > 3) {
if (navigator.onLine && window.ws && window.ws.readyState === 1) {
//发送消息
let timeunix = parseInt(new Date().getTime() / 1000 + "");
msg.timeunix = timeunix;
msg.b.timeunix = timeunix;
let str = JSON.stringify(msg);
let msgEnc = encStr(str);
window.ws.send(msgEnc)
if (show_log) {
console.info("↑ WS消息重发", msg);
}
}
}
}
let retryTime = 3000;
if (window.msgQueue.length > 0) {
retryTime = 1000;
}
setTimeout(function () {
checkQueue();
}, retryTime);
}

checkQueue();

if (msgObj.c === 666) {
if (window.msgQueue.length > 0) {
console.info("移除前-队列消息条数", window.msgQueue.length)
for (let i = window.msgQueue.length - 1; i >= 0; i--) {
let msgitem = window.msgQueue[i];
if (msgitem.mid === msgObj.mid) {
if (show_log) {
console.info("队列移除消息", i, msgitem)
}
window.msgQueue.splice(i, 1);
}
}
if (show_log) {
console.info("移除后-队列消息条数", window.msgQueue.length)
}
}
}