JavaScript 数组操作方法大全

数组操作是 JavaScript 中非常重要也非常常用的技巧。本文整理了常用的数组操作方法(包括 ES6 的 map、forEach、every、some、filter、find、from、of 等),熟悉了这些数组操作方法,编写程序也会更加简洁高效。

push()可以将某些值加入到数组的最后一个位置,不限制添加数量,添加的内容使用逗号隔开即可,加入后数组长度会增加。

使用push()后会改变原本的数组内容。

pop()会移除 (取出) 数组的最后一个元素。

使用pop()后会改变原本的数组内容。

shift()会移除 (取出) 数组的第一个元素。

使用shift()后会改变原本的数组内容。

unshift()会将指定的元素添加到第一个位置。

使用nushift()后会改变原本的数组内容。

reverse()会将数组反转。

使用push()后会改变原本的数组内容。

splice()可以移除或新增数组的元素,它包含了三个参数,第一个是要移除或要添加的序列号码 (必填),第二个是要移除的长度 ( 选填,若不填则第一个号码位置后方的所有元素都会被移除,若设定为 0 则不会有元素被移除 ),第三个是要添加的内容 ( 选填 )

使用splice()后会改变原本的数组内容。

设定第三个参数就能够添加或替代元素。

sort()可以针对数组的元素进行排序,里头包含了一个排序用的判断函数,函数内必须包含两个参数,这两个参数分别代表数组里第 n 个和第 n+1 个元素,通过比较第 n 和第 n+1 个元素的大小来进行排序。

使用sort()后会改变原本的数组内容。

如果不使用判断函数,默认会将元素转换成字符串,并采用 unicode 来判断,这也会造成某些数字的排序错误。

copyWithin()能复制数组中的某些元素,并将它们放到并取同一个数组指定的位置,copyWithin()有三个参数,第一个是要替换的位置 (必填),第二个是从什么位置开始复制 ( 选填,默认 0 ),第三个是停止复制的元素的前一个位置 ( 选填,默认等于数组长度 )。

使用copyWithin()后会改变原本的数组内容。

fill()会把数组中所有元素,替换为指定的值,fill()有三个参数,第一个是准备要替换的内容 (必填),第二个是从什么位置开始替换 ( 选填,不设定就全部替换 ),第三个是停止替换的元素的前一个位置 ( 选填,默认等于数组长度 )。

使用fill()会改变原本的数组内容。

length可以取得数组的长度 (所有元素的数量)。

indexOf()会判断数组中是否包含某个值,判断的方式为「由左而右」,如果有包含就返回这个值在数组中的索引值,如果没有就返回 -1,有两个参数,第一个参数表示要判断的值 (必填),第二个参数表示从数组的哪个位置开始判断 ( 选填,默认为 0 )。

lastIndexOf()会判断数组中是否包含某个值,判断的方式为「由右而左」,如果有包含就返回这个值在数组中的索引值,如果没有就返回 -1,有两个参数,第一个参数表示要判断的值 (必填),第二个参数表示判断到数组的哪个位置 ( 选填,默认为整个数组长度 – 1 )。

find()会将数组中的「每一个」元素带入指定的函数内做判断,并会返回第一个符合判断条件的元素,如果没有元素符合则会返回 undefined。

findIndex()会将数组中的「每一个」元素带入指定的函数内做判断,并会返回第一个符合判断条件元素的位置号码,如果没有元素符合则会返回 -1。

filter()会将数组中的「每一个」元素带入指定的函数内做判断,如果元素符合判断条件则会返回,成为一个新的数组元素。

forEach()会将数组中每个元素套用到指定的函数里进行运算,函数有三个参数,第一个参数表示每个元素的值 (必填),第二个参数为该元素的索引值 ( 选填 ),第三个参数则表示原本的数组 ( 选填 )。

如果通过第二和第三个参数搭配,就能做到改变原本数组的效果。

join()可以将数组中所有元素,由指定的字符合并在一起变成字符串呈现,若没有指定字符默认会用「逗号」合并。

concat()可以将两个数组合并在一起,如果是使用 ES6 语法也可以用扩展运算符来代替。

slice()可以截取出数组某部份的元素为一个新的数组,有两个必填的参数,第一个是起始位置,第二个是结束位置 (操作时数字减 1)。

map()会处理数组中每个元素,最后返回出一个新的数组,里头有一个函数 (必填) 和一个返回函数里的 this 参数 ( 选填 ),函数内又包含三个参数,第一个是每个元素的值 ( 必填 ),第二个是当前元素的索引值 ( 选填 ),第三个是当前的数组 ( 选填 )。

套用第二个和第三个参数的变化

如果要使用返回函数里 this 的参数,则「不能使用」箭头函数,因为箭头函数的 this 指向和函数的 this 指向不同,所以要用一般的函数处理。

reduce()可以将数组中每个元素进行计算,每次计算的结果会再与下个元素作计算,直到结束为止,里头包含一个函数 (必填) 和初始计算的数值 ( 选填 ),函数内有四个参数,第一个是计算的值 ( 必填 ),第二个是取得的元素 ( 必填 ),第三个是该元素的索引值 ( 选填 ),第四个是原本的数组 ( 选填 )。

reduceRight()和reduce()大同小异,只是其计算方式是由右到左,对于加法来说没什么影响,但对于减法而言就有差异。

flat()可以将一个多维数组的深度转成一维 (扁平化),它有一个选填的参数,代表要转换的深度数字,默认为 1,如果深度有很多层,可使用Infinity来全部展开成一维数组。

flatMap()的方法等于map()flat()的组合,在运算后直接将数组扁平化处理。

Array.isArray()能判断一个对象是否为数组,如果是就返回 true,不然就返回 false。

Array.from()会将「类数组对象」或是「可迭代的对象」转换成数组,Array.from()有两个参数,第一个参数为「类数组对象」或「可迭代的对象」(必填),第二个参数则是改变转换成数组元素的函数 ( 选填 )。

类数组对象具有 length 属性以及索引化 index 的元素,可迭代对象表示具有可以利用迭代的方式取得它自己本身的元素,例如 Map 和 Set… 等。(参考 MDN 说法)

类数组对象写法必须包含 length 属性,且对象 key 须为 0 开始的数字,对应转换后的元素索引。

Array.of()可以快速将数字、字符串等内容,转换成数组。

toString()会把整个数组转换成文字。

every()会将数组中的「每一个」元素带入指定的函数内做判断,只要有任何一个元素不符合判断条件,会返回 false,如果全部符合,就会返回 true。

some()会将数组中的「每一个」元素带入指定的函数内做判断,只要有任何一个元素符合判断条件,就会返回 true,如果全都不符合,就会返回 false。

includes()会判断数组中是否包含某个值,如果有包含就返回 true,否则返回 false,有两个参数,第一个参数表示要判断的值 (必填),第二个参数表示从数组的哪个位置开始判断 ( 选填 )。

valueOf()会返回数组的原始值,如果原本的数组有修改,那么返回的原始值也会跟着改变

keys()会返回数组中的每一个索引值 (key) 成为一个新的 Array Iterator 对象,因为是 Array Iterator 对象,可以通过for…of来取得。

javascript2

12种JavaScript中最常用的数组操作整理汇总

数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出 JavaScript 中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。

大多数人都知道可以像这样得到数组的长度:

有趣的是,我们可以手动修改长度。这就是我所说的:

甚至创建指定长度的新数组:

这不是一个很好的实践,但是值得了解。

我们常常需要清空数组时候会使用:

如果 arr 的值是共享的,并且所有参与者都必须看到清除的效果,那么这就是你需要采取的方法。

但是,JavaScript 语义规定,如果减少数组的长度,则必须删除新长度及以上的所有元素。

而且这需要花费时间(除非引擎对设置长度为零的特殊情况进行了优化)。实际上,一个性能测试表明,在所有当前的 JavaScript 引擎上,这种清除方法更快。

有几种方法可以解决这个问题。如果需要替换指定索引处的元素,请使用 splice:

splice 在数组删除有更多的说明

如果你需要根据项目的内容替换项目,或者必须创建一个新数组,请使用 map:

map 接受函数作为其参数。它将对数组中的每个元素调用该函数一次,并生成一个新的函数返回的项数组。

关于 map 有个经典的面试题:[\’1\’, \’2\’, \’3\’, \’4\’, \’5\’].map(parseInt) => ?

在某些情况下,你需要删除数组中的某些元素,然后创建一个新的元素。在这种情况下,使用在ES5中引入的很棒的 filter 方法:

filter 的工作原理与 map 非常相似。向它提供一个函数,filter 将在数组的每个元素上调用它。如果要在新数组中包含此特定元素,则函数必须返回 true,否则返回 false。

如果你想将多个数组合并为一个数组,有两种方法。

Array 提供了 concat 方法:

ES6 中引入了 spread operator,一种更方便的方法:

还有一种比较奇特方法:

上面 2 种通用的方法,都不会改变原数组,最后一种奇特方法,会改变 push 的原数组,谨慎使用。

Array.prototype.push.apply 和 concat 对比:

  • 数据上万情况下,两者性能相差毫秒个位数级别
  • Array.prototype.push.apply 数组长度有限制,不同浏览器不同,一般不能超过十万, concat 无限制
  • Array.prototype.push.apply 会改变原数组, concat 不会

正常情况下我们都应该使用 concat 和 spread operator,有种情况下可以使用,如果频繁合并数组可以用 Array.prototype.push.apply。

总所周知,定义数组变量存储不是数组值,而只是存储引用。 这是我的意思:

因为 arr2 持有对 arr1 的引用,所以对 arr2 的任何更改都是对 arr1 的更改。

我们也可以使用 ES6 的 spread operator:

也可以使用前面合并使用的 concat 方法

注意:如果想要了解更多的数组复制,请查询 数组深拷贝和浅拷贝 相关资料,这里只实现了浅拷贝。

数组去重是面试经常问的,数组去重方式很多,这里介绍比较简单直白的三种方法:

可以使用 filter 方法帮助我们删除重复数组元素。filter 将接受一个函数并传递 3 个参数:当前项、索引和当前数组。

可以使用 reduce 方法从数组中删除所有重复项。然而,这有点棘手。reduce 将接受一个函数并传递 2 个参数:数组的当前值和累加器。

累加器在项目之间保持相同,并最终返回:

可以使用 ES6 中引入的新数据结构 set 和 spread operator:

还有很多其他去重方式,比如使用 {} + for。

有时我们必须将一些其它数据结构,如集合或字符串转换为数组。

类数组:函数参数,dom 集合

字符串:

集合:

数组遍历方式很多,有底层的,有高阶函数式,我们就来介绍几种:

for:

for-in:

for-of:

forEach:

while:

迭代辅助语句:break 和 continue

  • break 语句是跳出当前循环,并执行当前循环之后的语句
  • continue 语句是终止当前循环,并继续执行下一次循环

上面方式中,除了 forEach 不支持跳出循环体,其他都支持。高阶函数式方式都类似 forEach 。

性能对比:

如果是编写一些库或者大量数据遍历,推荐使用 while。有名的工具库 lodash 里面遍历全是 while。正常操作,for-of 或者 forEach 已经完全满足需求。

下面介绍几种高级函数式,满足条件为 true 立即终止循环,否则继续遍历到整个数组完成的方法:

其他高阶函数式方法,例如 forEach map filter reduce reduceRight every sort 等,都是把整个数组遍历。

这个功能说不是很常用,但是有时候又会用到:

二维数组:

三维数组:

concat.apply 方式只能扁平化二维数组,在多了就需要递归操作。

ES6+(ES2019) 给我们提供一个 flat 方法:

默认只是扁平化二维数组,如果想要扁平化多维,它接受一个参数 depth,如果想要展开无限的深度使用 Infinity:

还有一种面试扁平化二维数组方式:

如何从数组中添加元素?

我们可以使用 push 从数组末尾添加元素,使用 unshift 从开头添加元素,或者使用 splice 从中间添加元素。concat 方法可创建带有所需项目的新数组,这是一种添加元素的更高级的方法。

从数组的末尾添加元素:

从数组的开头添加元素:

push 方法的工作原理与 unshift 方法非常相似,方法都没有参数,都是返回数组更新的 length 属性。它修改调用它的数组。

使用 splice 添加数组元素:

只需要把 splice,第二个参数设为 0 即可,splice 在数组删除有更多的说明

使用 concat 添加数组元素:

数组允许我们对值进行分组并对其进行遍历。 我们可以通过不同的方式添加和删除数组元素。 不幸的是,没有简单的 Array.remove 方法。

那么,如何从数组中删除元素?

除了 delete 方式外,JavaScript 数组还提供了多种清除数组值的方法。

我们可以使用 pop 从数组末尾删除元素,使用 shift 从开头删除元素,或者使用 splice 从中间删除元素。

filter 方法可创建带有所需项目的新数组,这是一种删除不需要的元素的更高级的方法。

从数组的末尾删除元素:

通过将 length 属性设置为小于当前数组长度,可以从数组末尾删除数组元素。 索引大于或等于新长度的任何元素都将被删除。

pop 方法删除数组的最后一个元素,返回该元素,并更新length属性。pop 方法会修改调用它的数组,这意味着与使用 delete 不同,最后一个元素被完全删除并且数组长度减小。

从数组的开头删除元素:

shift 方法的工作原理与 pop 方法非常相似,只是它删除了数组的第一个元素而不是最后一个元素。

shift 和 pop 方法都没有参数,都是返回已删除的元素,更新剩余元素的索引,并更新 length 属性。它修改调用它的数组。如果没有元素,或者数组长度为 0,该方法返回 undefined。

使用 splice 删除数组元素:

splice 方法可用于从数组中添加、替换或删除元素。

splice 方法接收至少三个参数:

  • start:在数组中开始删除元素的位置
  • deleteCount:删除多少个元素(可选)
  • items…:添加元素(可选)

splice 可以实现添加、替换或删除。

删除:

如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。

如果 deleteCount 被省略了,或者它的值大于等于array.length – start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。

如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。

添加:

添加只需要把 deleteCount 设置为 0,items 就是要添加的元素。

替换:

添加只需要把 deleteCount 设置为和 items 个数一样即可,items 就是要添加的元素。

注意:splice 方法实际上返回两个数组,即原始数组(现在缺少已删除的元素)和仅包含已删除的元素的数组。如果循环删除元素或者多个相同元素,最好使用倒序遍历。

使用 delete 删除单个数组元素:

使用 delete 运算符不会影响 length 属性。它也不会影响后续数组元素的索引。数组变得稀疏,这是说删除的项目没有被删除而是变成 undefined 的一种奇特的方式。

实际上没有将元素从数组中删除的原因是 delete 运算符更多的是释放内存,而不是删除元素。 当不再有对该值的引用时,将释放内存。

使用数组 filter 方法删除匹配的元素:

与 splice 方法不同,filter 创建一个新数组。

filter 接收一个回调方法,回调返回 true 或 false。返回 true 的元素被添加到新的经过筛选的数组中。

清除或重置数组:

最简单和最快的技术是将数组变量设置为空数组

清除数组的一个简单技巧是将其 length 属性设置为 0。

使用 splice 方法,不传递第二个参数。这将返回原始元素的一个副本,这对于我们的有些场景可能很方便。也是一种数组复制方法技巧。

使用 while 循环,这不是一种常用清除数组的方法,但它确实有效,而且可读性强。一些性能测试也显示这是最快的技术。

剔除假值:

是否有一个真值:

是否全部都是真值:

补零:

数组最大值和最小值:

判断回文字符串:

数组模拟队列:

队列先进先出:

获取数组最后一个元素:

像我们平常都是这样来获取:

感觉很麻烦,不过 ES 有了提案,未来可以通过 arr[-1] 这种方式来获取,Python 也有这种风骚的操作:

目前我们可以借助 ES6 的 Proxy 对象来实现:

注意:这样方式虽然有趣,但是会引起性能问题,50万次循环下,在Chrome浏览器,代理数组的执行时间大约为正常数组的50倍,在Firefox浏览器大约为20倍。在大量循环情况下,请慎用。无论是面试还是学习,你都应该掌握 Proxy 用法。

JavaScript 中常用的数组操作方法

数组是编程中很重要的数据结构。它允许我们以有序的方式存储和访问多个值(元素),从而实现更方便和高效的数据存储和读取操作。因而数组操作方法在任何语言中都尤为重要。这里总结 JavaScript 中数组的一些常用操作方法。

JavaScript 在 ES5 或者更早的版本中数组的方法相对较少,ES6后逐渐完善,操作方法可选的增多。

  1. concat(): 将两个或多个数组合并成一个新数组。
  2. join(): 将数组中所有元素组合成一个字符串。
  3. indexOf(): 返回数组中指定元素的第一个索引(如果存在),如果不存在,则返回-1。
  4. lastIndexOf(): 返回数组中指定元素的最后一个索引(如果存在),如果不存在,则返回-1。
  5. push(): 将一个元素添加到数组末尾。
  6. pop(): 删除数组中的最后一个元素。
  7. shift(): 删除数组中的第一个元素。
  8. unshift(): 将一个元素添加到数组的开头。
  9. slice(): 返回一个包含数组中指定部分的新数组。
  10. splice(): 用新元素替换数组中的元素或者移除元素。
  11. reverse(): 反转数组中的元素的顺序。
  12. sort(): 对数组进行排序。
  13. filter(): 返回一个新数组,包含执行指定函数后返回值为 true 的元素。
  14. map(): 返回一个新数组,包含对原数组中每个元素执行指定函数后的返回值。
  15. forEach(): 对数组中的每个元素执行指定函数,没有返回值。
  16. reduce(): 通过指定函数,从左到右依次执行并将数组简化为单一的值.
  17. every() – 判断是否所有元素都符合指定的条件
  18. some() – 判断是否至少有一个元素符合指定的条件

这里列举都是非常常见,使用频率较高的数组操作方法,它们可以方便地实现数组的增删改查、排序、过滤、遍历、映射等操作,非常有用。

ES6 中新增了一系列的数组操作方法,下面列举其中一些比较常用的:

  1. Array.from(): 可以将一个类数组对象或者可迭代对象转换成真正的数组。
  2. Array.of(): 用于创建一个包含任意数量元素的数组,不管元素的类型和数量。
  3. Array.prototype.copyWithin(): 在数组内部,将一段元素序列拷贝到另一段位置,覆盖原有的值。
  4. Array.prototype.fill(): 用一个固定值填充数组中从起始索引到终止索引内的全部元素。
  5. Array.prototype.find(): 返回数组中满足提供的测试函数的第一个元素的值。如果没有满足条件的元素,则返回 undefined。
  6. Array.prototype.findIndex(): 返回数组中满足提供的测试函数的第一个元素的索引。如果没有满足条件的元素,则返回 -1。
  7. Array.prototype.includes(): 判断数组中是否包含某个元素,返回一个 Boolean 值。
  8. Array.prototype.flat(): 将嵌套的数组“扁平化”,变为一维数组。
  9. Array.prototype.flatMap(): 类似于 map + flat,可以对数组中的每个元素执行一个回调函数,并将回调函数返回的结果转换成一维数组。
  10. Array.prototype.entries(): 返回一个包含数组中每个索引(以及相应的元素)的迭代器对象。
  11. Array.prototype.keys(): 返回一个包含数组中每个索引的迭代器对象。
  12. Array.prototype.values(): 返回一个包含数组中每个值的迭代器对象。

这三个方法都是用来判断数组是否符合指定条件的方法,它们的不同在于返回的结果和判断条件的方式。

Include 方法用来判断数组中是否包含指定元素,如果包含则返回 true,否则返回 false。它的使用方式如下:

some 方法用来判断数组中是否至少有一个元素符合指定的条件,如果符合则返回 true,否则返回 false。它的使用方式如下:

every 方法用来判断数组中是否所有的元素都符合指定的条件,如果都符合则返回 true,否则返回 false。它的使用方式如下:

再来一个例子:

注意:some() 和 every() 方法的参数是一个判断条件的函数,该函数会被传入每个数组元素,返回值为布尔值。some() 和 every() 的区别在于判断条件的方式,some() 符合任意一个就返回 true,every() 必须符合每一个才返回 true。

Array.prototype.find() 和 Array.prototype.filter() 它们都是基于一个返回值为布尔类型的回调函数来进行处理。

  • find(callback [, thisArg]): 返回数组中满足指定条件的第一个元素,如果没有找到,则返回 undefined。该方法通过调用指定的回调函数对数组中的每个元素进行测试,回调函数会接收三个参数:当前元素的值、当前元素的索引和数组本身,回调函数中可以定义任何需要满足的条件来判断是否需要返回当前元素。
  • filter(callback [, thisArg]): 返回一个新的数组,其中包含原始数组中通过指定回调函数测试的所有元素。该方法通过调用指定的回调函数对数组中的每个元素进行测试,回调函数会接收三个参数:当前元素的值、当前元素的索引和数组本身,回调函数中可以定义任何需要满足的条件来过滤元素。

find() 和 filter() 方法的区别在于,find() 方法返回数组中满足条件的第一个元素及其相关信息,而 filter() 方法返回数组中所有满足条件的元素的集合。例如:

在上述例子中,find() 方法返回第一个满足条件 item > 2 的元素 3,而 filter() 方法返回值为一个数组,其中包含所有满足条件 item > 2 的元素,即 [3, 4, 5]。

这两个方法都非常实用,在实际开发中应用广泛,可以通过一个简单的回调函数处理数组并对其进行搜索、过滤和选择等操作。

map 用于对数组中的每个元素执行指定的操作并返回一个新数组。map() 方法接受一个函数作为参数,该函数会被传入数组中的每个元素作为第一个参数,当前元素在数组中的索引作为第二个参数(可选),它返回的值会成为新数组中对应位置的值。

下面是一个简单的使用 map() 方法的例子,它将数组中的每个元素都乘以 2:

在这个例子中,我们将数组 [1, 2, 3, 4] 作为 map() 的方法调用者,传入一个箭头函数作为参数,箭头函数的作用是将数组中的每个元素都乘以 2。然后, map() 方法会返回一个新数组 [2, 4, 6, 8]。

map() 方法常用于从一组数据中选择需要的字段或将一组数据转换为另一种数据结构。

来一个例子:

在这个例子中,我们将包含一些数字的数组 numArr 作为 map() 方法调用者,传入一个返回对象的箭头函数。通过使用 map() 方法和箭头函数,我们将数字数组转换为对象数组,并且添加了一个 isEven 属性,该属性用于判断数字是否为偶数。最终输出的结果如下:

在这个例子中,我们将数字数组转换为对象数组,我们可以使用 map() 和箭头函数对任何数据进行转换,例如字符串数组、日期对象等,只需要根据实际情况编写对应的转换逻辑,很好用。请记住这个方法

这三个方法都可以用于遍历数组,它们返回的都是迭代器对象。通过结合使用这三种方法,我们可以很方便地遍历获取数组的键和值,以及相应的索引。

下面是一个使用 entries(), keys() 和 values() 遍历打印数组的例子:

在这个例子中,我们使用 entries(), keys() 和 values() 方法获取数组的迭代器对象,并分别使用 for…of 循环遍历每个键值对或键或值,并将索引和值打印出来。最终输出如下:

通过结合使用 entries(), keys() 和 values(),我们可以很方便地遍历获取数组的索引、键和值,并进行相应的操作。在实际开发中,我们可以根据具体的需求,选择合适的方法和操作方案。

这两个都是 ECMAScript 6 新增的数组方法。

  • Array.from(arrayLike [, mapFn [, thisArg]]): 该方法通过构建一个新数组来从类似数组或可迭代对象中创建数组。第一个参数接收一个类数组对象或可迭代对象,用来生成新的数组。可选参数mapFn表示对每个数组元素进行的映射操作的回调函数,thisArg用于指定回调函数中 this 关键字的取值。如果没有指定第二个参数,则以原样复制每个元素。
  • Array.of(element0[, element1[, …[, elementN]]]): 该方法通过将传入的参数创建并返回一个新的数组。传入的参数将成为新数组的元素,参数个数可以任意。如果没有参数,则返回一个空数组。

示例如下:

在上面的例子中,我们使用 Array.from() 将字符串中的单个字符存储到一个新数组中,同时以不同的方式使用了映射函数。此外,我们使用 Array.of() 创建了三个新数组,并分别使用了零个、三个和两个参数。

运行效果如下:

JavaScript 数组还支持其他一些操作方法,这里仅仅分享日常应该比较多的。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。