【前端】JavaScript中的常见的数组方法、类数组对象、数组的常见操作

以下内容来自其他网站复制粘贴:2. 重学 JavaScript

一、数组方法

1、操作方法

        对于数组,还有很多操作方法,下面我们就来看看常用的concat()、slice()、splice()方法。
(1)concat()
        concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。其适用语法如下:

arrayObject.concat(arrayX,arrayX,......,arrayX)

其中参数arrayX是必需的。该参数可以是具体的值,也可以是数组对象。可以是任意多个。

使用示例如下:

let array = [1, 2, 3];
let array2 = array.concat(4, [5, 6], [7, 8, 9]);
console.log(array2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array);  // [1, 2, 3], 可见原数组并未被修改

该方法还可以用于数组扁平化,后面会介绍。

(2)slice()

        slice() 方法可从已有的数组中返回选定的元素。返回一个新的数组,包含从 start 到 end (不包括该元素)的数组元素。方法并不会修改数组,而是返回一个子数组。其使用语法如下:

arrayObject.slice(start,end)

其参数如下:

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

使用示例如下:

let array = ["one", "two", "three", "four", "five"];
console.log(array.slice(0));    // ["one", "two", "three","four", "five"]
console.log(array.slice(2,3)); // ["three"]
(3)splice()

        splice()方法可能是数组中的最强大的方法之一了,使用它的形式有很多种,它会向/从数组中添加/删除项目,然后返回被删除的项目。该方法会改变原始数组。其使用语法如下:

arrayObject.splice(index, howmany, item1,.....,itemX)

其参数如下:

  • index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
  • howmany:必需。要删除的项目数量。如果设置为 0,则不会删除项目。
  • item1, ..., itemX:可选。向数组添加的新项目。

从上面参数可知,splice主要有三种使用形式:

  • 删除:需要给splice()传递两个参数,即要删除的第一个元素的位置和要删除的元素的数量;
  • 插入:需要给splice()传递至少三个参数,即开始位置、0(要删除的元素数量)、要插入的元素。
  • 替换:splice()方法可以在删除元素的同事在指定位置插入新的元素。同样需要传入至少三个参数,即开始位置、要删除的元素数量、要插入的元素。要插入的元素数量是任意的,不一定和删除的元素数量相等。

使用示例如下:

let array = ["one", "two", "three","four", "five"];
console.log(array.splice(1, 2));           // 删除:["two", "three"]

let array = ["one", "two", "three","four", "five"];
console.log(array.splice(2, 0, 996));      // 插入:[]

let array = ["one", "two", "three","four", "five"];
console.log(array.splice(2, 1, 996));      // 替换:["three"]
2、归并方法

        ECMAScript为数组提供了两个归并方法:reduce()和reduceRight()。下面就分别来看看这两个方法。

(1)reduce()

        reduce() 方法对数组中的每个元素执行一个reducer函数(升序执行),将其结果汇总为单个返回值。其使用语法如下:

arr.reduce(callback,[initialValue])

        educe 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

(1) callback (执行数组中每个值的函数,包含四个参数)

  • previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
  • currentValue (数组中当前被处理的元素)
  • index (当前元素在数组中的索引)
  • array (调用 reduce 的数组)

(2) initialValue (作为第一次调用 callback 的第一个参数。)

let arr = [1, 2, 3, 4]
let sum = arr.reduce((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

再来加一个初始值看看:

let arr = [1, 2, 3, 4]
let sum = arr.reduce((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
}, 5)
console.log(arr, sum);  

输出结果如下:

5 1 0
6 2 1
8 3 2
11 4 3
[1, 2, 3, 4] 15

        通过上面例子,可以得出结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。

注意,该方法如果添加初始值,就会改变原数组,将这个初始值放在数组的最后一位

(2)reduceRight()

        该方法和的上面的reduce()用法几乎一致,只是该方法是对数组进行倒序查找的。而reduce()方法是正序执行的。

let arr = [1, 2, 3, 4]
let sum = arr.reduceRight((prev, cur, index, arr) => {
    console.log(prev, cur, index);
    return prev + cur;
}, 5)
console.log(arr, sum);

输出结果如下:

5 4 3
9 3 2
12 2 1
14 1 0
[1, 2, 3, 4] 15
3. 搜索和位置方法

ECMAScript提供了两类搜索数组的方法:按照严格相等搜索和按照断言函数搜索。

(1)严格相等

        ECMAScript通过了3个严格相等的搜索方法:indexOf()、lastIndexOf()、includes()。这些方法都接收两个参数:要查找的元素和可选的其实搜索位置。lastIndexOf()方法会从数组结尾元素开始向前搜索,其他两个方法则会从数组开始元素向后进行搜索。indexOf()和lastIndexOf()返回的是查找元素在数组中的索引值,如果没有找到,则返回-1。includes()方法会返回布尔值,表示是否找到至少一个与指定元素匹配的项。在比较第一个参数和数组的每一项时,会使用全等(===)比较,也就是说两项必须严格相等。

使用示例如下:

let arr = [1, 2, 3, 4, 5];
console.log(arr.indexOf(2))      // 1
console.log(arr.lastIndexOf(3))  // 2
console.log(arr.includes(4))     // true
(2)断言函数

        ECMAScript也允许按照定义的断言函数搜索数组,每个索引都会调用这个函数,断言函数的返回值决定了相应索引的元素是否被认为匹配。使用断言函数的方法有两个,分别是find()和findIndex()方法。这两个方法对于空数组,函数是不会执行的。并且没有改变数组的原始值。他们的都有三个参数:元素、索引、元素所属的数组对象,其中元素是数组中当前搜索的元素,索引是当前元素的索引,而数组是当前正在搜索的数组。

        这两个方法都从数组的开始进行搜索,find()返回的是第一个匹配的元素,如果没有符合条件的元素返回 undefined;findIndex()返回的是第一个匹配的元素的索引,如果没有符合条件的元素返回 -1。

let arr = [1, 2, 3, 4, 5]
arr.find(item => item > 2)      // 结果: 3
arr.findIndex(item => item > 2) // 结果: 2
4、 迭代器方法

        在ES6中,Array的原型上暴露了3个用于检索数组内容的方法:keys()、values()、entries()。keys()方法返回数组索引的迭代器,values()方法返回数组元素的迭代器,entries()方法返回索引值对的迭代器。

        使用示例如下(因为这些方法返回的都是迭代器,所以可以将他们的内容通过Array.from直接转化为数组实例):

let array = ["one", "two", "three", "four", "five"];
console.log(Array.from(array.keys()))     // [0, 1, 2, 3, 4]
console.log(Array.from(array.values()))   // ["one", "two", "three", "four", "five"]
console.log(Array.from(array.entries()))  // [[0, "one"], [1, "two"], [2, "three"], [3, "four"], [4, "five"]]
5、 迭代方法

        ECMAScript为数组定义了5个迭代方法,分别是every()、filter()、forEach()、map()、some()。这些方法都不会改变原数组。这五个方法都接收两个参数:以每一项为参数运行的函数和可选的作为函数运行上下文的作用域对象(影响函数中的this值)。传给每个方法的函数接收三个参数,分别是当前元素、当前元素的索引值、当前元素所属的数对象。

(1)forEach()

        forEach 方法用于调用数组的每个元素,并将元素传递给回调函数。该方法没有返回值,使用示例如下:

let arr = [1,2,3,4,5]
arr.forEach((item, index, arr) => {
  console.log(index+":"+item)
})

        该方法还可以有第二个参数,用来绑定回调函数内部this变量(回调函数不能是箭头函数,因为箭头函数没有this):

(2)map()

        map() 方法会返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。该方法按照原始数组元素顺序依次处理元素。该方法不会对空数组进行检测,它会返回一个新数组,不会改变原始数组。使用示例如下

let arr = [1, 2, 3];
 
arr.map(item => {
    return item+1;
})
// 结果: [2, 3, 4]

第二个参数用来绑定参数函数内部的this变量:

var arr = ['a', 'b', 'c'];
 
[1, 2].map(function (e) {
    return this[e];
}, arr)
 // 结果: ['b', 'c']

该方法可以进行链式调用:

let arr = [1, 2, 3];
 
arr.map(item => item+1).map(item => item+1)
 // 结果: [3, 4, 5]

forEach和map区别如下:

  • forEach()方法:会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
  • map()方法:不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;
(3)filter()

        filter()方法用于过滤数组,满足条件的元素会被返回。它的参数是一个回调函数,所有数组元素依次执行该函数,返回结果为true的元素会被返回。该方法会返回一个新的数组,不会改变原数组。

let arr = [1, 2, 3, 4, 5]
arr.filter(item => item > 2) 
// 结果:[3, 4, 5]

可以使用filter()方法来移除数组中的undefined、null、NAN等值

let arr = [1, undefined, 2, null, 3, false, '', 4, 0]
arr.filter(Boolean)
// 结果:[1, 2, 3, 4]
(4)every()

        该方法会对数组中的每一项进行遍历,只有所有元素都符合条件时,才返回true,否则就返回false。

let arr = [1, 2, 3, 4, 5]
arr.every(item => item > 0) 
// 结果: true
(5)some()

        该方法会对数组中的每一项进行遍历,只要有一个元素符合条件,就返回true,否则就返回false。

let arr = [1, 2, 3, 4, 5]
arr.some(item => item > 4) 
// 结果: true

6、其他方法

除了上述方法,遍历数组的方法还有for...in和for...of。下面就来简单看一下。

(1)for…in

        for…in 主要用于对数组或者对象的属性进行循环操作。循环中的代码每执行一次,就会对对象的属性进行一次操作。其使用语法如下:

for (var item in object) {
  执行的代码块
}

其中两个参数:

  • item:必须。指定的变量可以是数组元素,也可以是对象的属性。
  • object:必须。指定迭代的的对象。

使用示例如下:

const arr = [1, 2, 3]; 
 
for (var i in arr) { 
    console.log('键名:', i); 
    console.log('键值:', arr[i]); 
}

输出结果如下:
 

键名: 0
键值: 1
键名: 1
键值: 2
键名: 2
键值: 3

需要注意,该方法不仅会遍历当前的对象所有的可枚举属性,还会遍历其原型链上的属性。除此之外,该方法遍历数组时候,遍历出来的是数组的索引值,遍历对象的时候,遍历出来的是键值名。

(2)for...of

        for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...inforEach() ,并支持新的迭代协议。for...of 允许遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。

语法:
 

for (var item of iterable) {
    执行的代码块
}

其中两个参数:

  • item:每个迭代的属性值被分配给该变量。
  • iterable:一个具有可枚举属性并且可以迭代的对象。

该方法允许获取对象的键值:

var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
  console.log(a); // 0 1 2 3
}
for (let a of arr) {
  console.log(a); // a b c d
}

该方法只会遍历当前对象的属性,不会遍历其原型链上的属性。

注意:

  • for...of适用遍历 数组/ 类数组/字符串/map/set 等拥有迭代器对象的集合;
  • 它可以正确响应break、continue和return语句;
  • for...of循环不支持遍历普通对象,因为没有迭代器对象。如果想要遍历一个对象的属性,可以用for-in循环。

总结,for…of 和for…in的区别如下:

  • for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
  • 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;
(3)flat()

        在ES2019中,flat()方法用于创建并返回一个新数组,这个新数组包含与它调用flat()的数组相同的元素,只不过其中任何本身也是数组的元素会被打平填充到返回的数组中:

[1, [2, 3]].flat()   // [1, 2, 3]
[1, [2, [3, 4]]].flat()   // [1, 2, [3, 4]]

        在不传参数时,flat()默认只会打平一级嵌套,如果想要打平更多的层级,就需要传给flat()一个数值参数,这个参数表示要打平的层级数:

[1, [2, [3, 4]]].flat(2)   // [1, 2, 3, 4]

二、类数组对象

JavaScript 中一直存在一种类数组的对象,它们不能直接调用数组的方法,但是又和数组比较类似,在某些特定的编程场景中会出现,下面就来看一下什么是类数组。

在 JavaScript 中,主要有以下情况中的对象是类数组:

  • 函数里面的参数对象 arguments;
  • 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection;
  • 用 querySelector 获得的 NodeList。
1. 类数组概述
(1)arguments

        在日常开发中经常会遇到各种类数组对象,最常见的就是在函数中使用的 arguments,它的对象只定义在函数体中,包括了函数的参数和其他属性。先来看下 arguments 的使用方法:

function foo(name, age, sex) {
    console.log(arguments);
    console.log(typeof arguments);
    console.log(Object.prototype.toString.call(arguments));
}
foo('jack', '18', 'male');

打印结果如下:

        可以看到,typeof 这个 arguments 返回的是 object,通过 Object.prototype.toString.call 返回的结果是 [object arguments],而不是 [object array],说明 arguments 和数组还是有区别的。

        length 属性就是函数参数的长度。另外 arguments 还有一个 callee 属性,下面看看这个 callee 是干什么的:

function foo(name, age, sex) {
    console.log(arguments.callee);
}

foo('jack', '18', 'male');

打印结果如下:
 

ƒ foo(name, age, sex) {
    console.log(arguments.callee);
}

可以看出,输出的就是函数自身,如果在函数内部直接执行调用 callee,那它就会不停地执行当前函数,直到执行到内存溢出。

(2)HTMLCollection

HTMLCollection 简单来说是 HTML DOM 对象的一个接口,这个接口包含了获取到的 DOM 元素集合,返回的类型是类数组对象,如果用 typeof 来判断的话,它返回的是 object。它是及时更新的,当文档中的 DOM 变化时,它也会随之变化。

下面来 HTMLCollection 最后返回的是什么,在一个有 form 表单的页面中,在控制台中执行下述代码:

var elem1, elem2;
// document.forms 是一个 HTMLCollection
elem1 = document.forms[0];
elem2 = document.forms.item(0);
console.log(elem1);
console.log(elem2);
console.log(typeof elem1);
console.log(Object.prototype.toString.call(elem1));

打印结果如下:

        可以看到,这里打印出来了页面第一个 form 表单元素,同时也打印出来了判断类型的结果,说明打印的判断的类型和 arguments 返回的也比较类似,typeof 返回的都是 object,和上面的类似。

        注意:HTML DOM 中的 HTMLCollection 是即时更新的,当其所包含的文档结构发生改变时,它会自动更新。

(3)NodeList

        NodeList 对象是节点的集合,通常是由 querySlector 返回的。NodeList 不是一个数组,也是一种类数组。虽然 NodeList 不是一个数组,但是可以使用 for...of 来迭代。在一些情况下,NodeList 是一个实时集合,也就是说,如果文档中的节点树发生变化,NodeList 也会随之变化。

var list = document.querySelectorAll('input[type=checkbox]');
for (var checkbox of list) {
  checkbox.checked = true;
}
console.log(list);
console.log(typeof list);
console.log(Object.prototype.toString.call(list));

打印结果如下:

2. 类数组应用场景
(1)遍历参数操作

        在函数内部可以直接获取 arguments 这个类数组的值,那么也可以对于参数进行一些操作,比如下面这段代码可以将函数的参数默认进行求和操作:

function add() {
    var sum =0,
        len = arguments.length;
    for(var i = 0; i < len; i++){
        sum += arguments[i];
    }
    return sum;
}
add()                            // 0
add(1)                           // 1
add(1,2)                        // 3
add(1,2,3,4);                    // 10

        结合上面这段代码,在函数内部可以将参数直接进行累加操作,以达到预期的效果,参数多少也可以不受限制,根据长度直接计算,返回出最后函数的参数的累加结果,其他操作也类似。

(2)定义连接字符串函数

        可以通过 arguments 这个例子定义一个函数来连接字符串。这个函数唯一正式声明了的参数是一个字符串,该参数指定一个字符作为衔接点来连接字符串。该函数定义如下:

function myConcat(separa) {
  var args = Array.prototype.slice.call(arguments, 1);
  return args.join(separa);
}
myConcat(", ", "red", "orange", "blue");
// "red, orange, blue"
myConcat("; ", "elephant", "lion", "snake");
// "elephant; lion; snake"
myConcat(". ", "one", "two", "three", "four", "five");
// "one. two. three. four. five"

        这段代码说明可以传递任意数量的参数到该函数,并使用每个参数作为列表中的项创建列表进行拼接。从这个例子中也可以看出,可以在日常编码中采用这样的代码抽象方式,把需要解决的这一类问题,都抽象成通用的方法,来提升代码的可复用性。

(3)传递参数

可以借助apply 或 call 与 arguments 相结合,将参数从一个函数传递到另一个函数:

1. // 使用 apply 将 foo 的参数传递给 bar
2. function foo() {
3.     bar.apply(this, arguments);
4. }
5. function bar(a, b, c) {
6. console.log(a, b, c);
7. }
8. foo(1, 2, 3)   //1 2 3

上述代码中,通过在 foo 函数内部调用 apply 方法,用 foo 函数的参数传递给 bar 函数,这样就实现了借用参数的妙用。

3. 类数组转为数组
(1)借用数组方法

        类数组因为不是真正的数组,所以没有数组类型上自带的那些方法,所以就需要利用下面这几个方法去借用数组的方法。比如借用数组的 push 方法,代码如下:

        可以看到,arrayLike 其实是一个对象,模拟数组的一个类数组,从数据类型上说它是一个对象,新增了一个 length 的属性。还可以看出,用 typeof 来判断输出的是 object,它自身是不会有数组的 push 方法的,这里用 call 的方法来借用 Array 原型链上的 push 方法,可以实现一个类数组的 push 方法,给 arrayLike 添加新的元素。

        从打印结果可以看出,数组的 push 方法满足了我们想要实现添加元素的诉求。再来看下 arguments 如何转换成数组:

function sum(a, b) {
  let args = Array.prototype.slice.call(arguments);
 // let args = [].slice.call(arguments); // 这样写也是一样效果
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);  // 3
function sum(a, b) {
  let args = Array.prototype.concat.apply([], arguments);
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);  // 3

        可以看到,借用 Array 原型链上的各种方法,来实现 sum 函数的参数相加的效果。一开始都是将 arguments 通过借用数组的方法转换为真正的数组,最后都又通过数组的 reduce 方法实现了参数转化的真数组 args 的相加,最后返回预期的结果。

(2)借用ES6方法

        还可以采用 ES6 新增的 Array.from 方法以及展开运算符的方法来将类数组转化为数组。那么还是围绕上面这个 sum 函数来进行改变,看下用 Array.from 和展开运算符是怎么实现转换数组的:

function sum(a, b) {
  let args = Array.from(arguments);
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 3
function sum(a, b) {
  let args = [...arguments];
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 3
function sum(...args) {
  console.log(args.reduce((sum, cur) => sum + cur));
}
sum(1, 2);    // 3

        可以看到,Array.from 和 ES6 的展开运算符,都可以把 arguments 这个类数组转换成数组 args,从而实现调用 reduce 方法对参数进行累加操作。其中第二种和第三种都是用 ES6 的展开运算符,虽然写法不一样,但是基本都可以满足多个参数实现累加的效果。

三、数组常见操作

1、数组扁平化

        下面再来看看数组的扁平化。所谓扁平化,其实就是将一个嵌套多层的数组 array(嵌套可以是任何层数)转换为只有一层的数组。举个简单的例子,假设有个名为 flatten 的函数可以做到数组扁平化,那么输出效果如下:

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

简单来说就是把多维的数组“拍平”,输出最后的一维数组。下面来看看实现flatten函数的方式。

(1)递归实现

        普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果某一项还是一个数组,那么就继续往下遍历,利用递归来实现数组的每一项的连接:

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
  let result = [];

  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}
flatten(arr);  //  [1, 2, 3, 4,5]

        可以看到,最后返回的结果是扁平化的结果,这段代码核心就是循环遍历过程中的递归操作,就是在遍历过程中发现数组元素还是数组的时候进行递归操作,把数组的结果通过数组的 concat 方法拼接到最后要返回的 result 数组上,那么最后输出的结果就是扁平化后的数组。

(2)reduce 函数迭代

        从上面的递归函数可以看出,其实就是对数组的每一项进行处理,那么其实也可以用 reduce 来实现数组的拼接,从而简化上面方法的代码,改造后的代码如下:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(arr));//  [1, 2, 3, 4,5]

        这段代码在控制台执行之后,也可以得到想要的结果。上面我们说了 reduce 的第一个参数用来返回最后累加的结果,思路和第一种递归方法是一样的,但是通过使用 reduce 之后代码变得更简洁了,也同样解决了扁平化的问题。

(3)扩展运算符实现

这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

        从执行的结果中可以发现,先用数组的 some 方法把数组中仍然是组数的项过滤出来,然后执行 concat 操作,利用 ES6 的展开运算符,将其拼接到原数组中,最后返回原数组,达到了预期的效果。

(4)split 和 toString

        可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:        

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
    return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。

(5)ES6 中的 flat

我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])

其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]
(6)正则和 JSON 方法

        在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
  let str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, '');
  str = '[' + str + ']';
  return JSON.parse(str); 
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

        可以看到,其中先把传入的数组转换成字符串,然后通过正则表达式的方式把括号过滤掉,匹配规则是:全局匹配(g)左括号或者右括号,将它们替换成空格,最后返回处理后的结果。之后拿着正则处理好的结果重新在外层包裹括号,最后通过 JSON.parse 转换成数组返回。

2. 数组去重

去除无序数组中的重复元素并且返回新的无重复数组。

(1)Set实现

ES6方法(使用数据结构集合):

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
(2)map实现

ES5方法:使用map存储不重复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

function uniqueArray(array) {
  let map = {};
  let res = [];
  for(var i = 0; i < array.length; i++) {
    if(!map.hasOwnProperty([array[i]])) {
      map[array[i]] = 1;
      res.push(array[i]);
    }
  }
  return res;
}

uniqueArray(array); // [1, 2, 3, 5, 9, 8]
3. 数组求和
(1)reduce实现
let arr = [1, 2, 3, 4, 5, 6]
let sum = arr.reduce( (total,i) => total += i,0);
console.log(sum);     // 21
(2)递归实现
let arr = [1, 2, 3, 4, 5, 6] 
function add(arr) {
    if (arr.length == 1) return arr[0] 
    return arr[0] + add(arr.slice(1)) 
}
console.log(add(arr))  // 21
4. 数组乱序
(1)正向遍历

主要的实现思路就是:

  1. 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换;
  2. 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换;
  3. 按照上面的规律执行,直到遍历完成。
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (var i = 0; i < arr.length; i++) {
  const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
  [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)
(2)倒序遍历

倒序遍历和上面实现思路类似,代码如下:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let length = arr.length,
    randomIndex,
    temp;
  while (length) {
    randomIndex = Math.floor(Math.random() * length--);
    temp = arr[length];
    arr[length] = arr[randomIndex];
    arr[randomIndex] = temp;
  }
console.log(arr)

四、数组的遍历方法

思维导图:

1、数组遍历法

方法

是否改变原数组特点
forEach()    没有返回值
map()有返回值,可链式调用
for offor...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环
filter()过滤数组,返回包含符合条件的元素的数组,可链式调用
every()、some()some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
find()、findIndex()find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce()、reduceRight()reduce()对数组正序操作;reduceRight()对数组逆序操作
keys()、values()、entries()keys() 返回数组的索引值;values() 返回数组元素;entries() 返回数组的键值对。

2、对象遍历法
对象方法

遍历基本属性

遍历原型链

遍历不可枚举属性遍历Symbol

for in

Object.keys()

Object.getOwnPropertyNames()

Object.getOwnPropertySymbols()

Reflect.ownKeys()

3、其他遍历法
1. for

for循环是应该是最常见的循环方式了,它由三个表达式组成,分别是声明循环变量、判断循环条件、更新循环变量。这三个表达式用分号分隔。可以使用临时变量将数组的长度缓存起来,避免重复获取数组长度,当数组较大时优化效果会比较明显。

const arr = [1,2,3,4,5]
for(let i = 0, len = arr.length; i < len; i++ ){
  console.log(arr[i])
}

在执行的时候,会先判断执行条件,再执行。for循环可以用来遍历数组,字符串,类数组,DOM节点等。可以改变原数组。

2. while

while循环中的结束条件可以是各种类型,但是最终都会转为布尔值,转换规则如下。

  • Boolean:true为真,false为假;
  • String:空字符串为假,所有非空字符串为真;
  • Number:0为假,非0数字为真;
  • null/Undefined/NaN:全为假;
  • Object:全为真。

let num = 1;
            
while (num < 10){
    console.log(num);
    num ++;
}

whilefor一样,都是先判断,再执行。只要指定条件为 true,循环就可以一直执行代码。

3. do / while

该方法会先执行再判断,即使初始条件不成立,do/while循环也至少会执行一次。

let num = 10;
            
do
	{
    console.log(num);
    num--;
  }
while(num >= 0);
            
console.log(num); //-1

不建议使用do / while来遍历数组。

4. for await of

for await...of方法被称为异步迭代器,该方法是主要用来遍历异步对象。它是ES2018中引入的方法。

for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,类数组,Map, Set和自定义的异步或者同步可迭代对象。这个语句只能在 async function内使用:

function Gen (time) {
  return new Promise((resolve,reject) => {
    setTimeout(function () {
       resolve(time)
    },time)
  })
}

async function test () {
   let arr = [Gen(2000),Gen(100),Gen(3000)]
   for await (let item of arr) {
      console.log(Date.now(),item)
   }
}
test()

输出结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值