学习链接

数组的常用方法有哪些

现代 JavaScript 教程

阮一峰:数组

阮一峰:数组的扩展

数据类型-Array

普通的对象允许存储键值集合,但却无法管理元素顺序,比如无法在已有的元素之间插入一个新的属性。

对象的键名遍历顺序,非负整数键名升序👉非数字字符键名的添加顺序👉Symbol 键名的添加顺序

为了解决元素的顺序管理问题,引入了数组这一数据结构。

数组,本质上是一种对象,用于存储和管理 有序集合,里面的元素都是按顺序排列的,并且提供 管理元素顺序 的方法

  1. [] // []
  2. new Array(0) // []
  3. Array(0) // []

数组内部

仍是对象

数组是一种特殊的对象。使用方括号来访问属性 arr[0] 实际上是来自于对象的语法。它其实与 obj[key] 相同,其中 arr 是对象,而数字用作键(key)。

数组是扩展了的对象,提供了特殊的方法来处理有序的数据集合以及 length 属性。但从本质上讲,它仍然是一个对象。

内部优化

数组真正特殊的是它们的内部实现。

JavaScript 引擎尝试把这些元素一个接一个地存储在连续的内存区域,同时还有一些其它的优化,以使数组运行得非常快

但是,如果我们不像“有序集合”那样使用数组,而是像常规对象那样使用数组,这些就都不生效了。

从技术上说,数组是基于对象的,可以给它们添加任何属性。

  1. let fruits = []; // 创建一个数组
  2. fruits[99999] = 5; // 分配索引远大于数组长度的属性
  3. fruits.age = 25; // 创建一个具有任意名称的属性

但是 JavaScript 引擎会发现,我们在像使用常规对象一样使用数组,那么针对数组的优化就不再适用了,然后对应的优化就会被关闭,这些优化所带来的优势也就荡然无存了。

数组误用的几种方式:

  • 添加一个非数字的属性,比如 arr.test = 5
  • 制造空洞,比如:添加 arr[0],然后添加 arr[1000] (它们中间什么都没有)。
  • 以倒序填充数组,比如 arr[1000]arr[999] 等等。

数组在 JavaScript 引擎内部是经过特殊调整的,使得更好地作用于连续的有序数据,所以最好将数组视为有序数据的数据结构,用正确的方式使用数组。

如果需要任意键值,那很有可能实际上我们需要的是常规对象 {}

方法性能

push/pop 方法运行的比较快,而 shift/unshift 比较慢。

pop / push 方法不需要额外移动任何东西,因为其它元素都保留了各自的索引。

shift / unshift 方法需要改变剩余所有元素的索引值,因此性能会比 pop / push 差。

清空数组

  1. arr.length = 0;

toString

数组有自己的 toString 方法的实现,会返回以逗号隔开的元素列表。

  1. let arr = [1, 2, 3];
  2. arr // 1,2,3
  3. arr.toString() === '1,2,3' // true
  1. [] + 1 // "1"
  2. [1] + 1 // "11"
  3. [1,2] + 1 // "1,21"

数组没有 Symbol.toPrimitive,也没有 valueOf,它们只能执行 toString 进行转换,所以这里 [] 就变成了一个空字符串,[1] 变成了 "1"[1,2] 变成了 "1,2"

"+" 运算符把一些项加到字符串后面时,加号后面的项也会被转换成字符串,所以下一步就会是这样:

  1. "" + 1 // "1"
  2. "1" + 1 // "11"
  3. "1,2" + 1 // "1,21"

数组方法

静态方法

Array.isArray()

返回布尔值,识别参数是否为数组。

Array.from()

可将类数组对象和可遍历对象对象转为数组。

Array.of()

将一组值转为数组,弥补 Array() 的不足。

实例方法

sort()

排序稳定性:排序关键字相同的项目,排序前后的顺序不变。

ES2019 规定 sort() 的默认排序算法是稳定的。

比较函数可以返回任何数字,正数表示 “大于”,一个负数表示 “小于”。

  1. arr.sort((a, b) => a - b);
  2. arr.sort((a, b) => b - a);
  3. // 正数: a “大于” b => a 在 b 后
  4. // 负数: a “小于” b => b 在 a 后

内部算法会在排序过程中,将一个元素与多个其他元素进行比较,但是它会尝试进行尽可能少的比较

includes()、find()、findIndex()

都可识别 NaN,弥补 indexOf 的不足

增删改查

方法 返回 对原数组产生影响
push(e0, e1,… , eN) length
unshift(e0, e1,… , eN) length
pop() removed element
shift() removed element
splice(start, deleteCount, e1, e2,…, eN) removed element array ✅ / ❌
concat(e0, e1,… , eN)/concat(array) new array
indexOf(element, fromIndex) index or -1
lastIndexOf(element, fromIndex) index or -1
includes(element, fromIndex) boolean
find((e, i, arr) => true/false, thisArg) element
findIndex((e, i, arr) => true/false, thisArg) index or -1
some((e, i, arr) => true/false, thisArg) boolean
every((e, i, arr) => true/false, thisArg) boolean

转换/排序

方法 返回 对原数组产生影响
sort(compareFn) original array
reverse() original array
join(separator) string

遍历数组

方法 返回 对原数组产生影响
forEach((e, i, arr) => true/false, thisArg) undefined
filter((e, i, arr) => true/false, thisArg) new array
map((e, i, arr) => element, thisArg) new array
fill(value, start, end) original array
reduce((pre,cur, i, arr) => val, initVal) value

补充

驼峰转换

  1. const camelize = str => {
  2. return str
  3. .split('-')
  4. .map((element, index) =>
  5. index === 0 ?
  6. element :
  7. element[0].toUpperCase() + element.slice(1))
  8. .join('');
  9. };
  10. camelize("background-color") // backgroundColor

数组去重 ( 性能问题 )

  1. function unique(arr) {
  2. let result = [];
  3. for (let str of arr) {
  4. if (!result.includes(str)) {
  5. result.push(str);
  6. }
  7. }
  8. return result;
  9. }
  10. let strings = ["Hare", "Krishna", "Hare", "Krishna",
  11. "Krishna", "Krishna", "Hare", "Hare", ":-O"];
  12. unique(strings) // Hare, Krishna, :-O

代码有效,但其中存在潜在的性能问题。

方法 result.includes(str) 在内部遍历数组 result,并将每个元素与 str 进行比较以找到匹配项

所以如果 result 中有 100 个元素,并且没有任何一项与 str 匹配,那么它将遍历整个 result 并进行 100 次比较。如果 result 很大,比如 10000,那么就会有 10000 次的比较。

这本身并不是问题,因为 JavaScript 引擎速度非常快,所以遍历一个有 10000 个元素的数组只需要几微秒。

但是我们在 for循环中对 arr 的每个元素都进行了一次检测。

因此,如果 arr.length10000,我们会有 10000 * 10000 = 1 亿次的比较。那真的太多了。

所以该解决方案仅适用于小型数组。