JavaScript学习笔记:数组

数组的定义

  • 数组是值的有序集合,其中的值叫做元素,每个元素都有一个用数值表示的位置,称作索引或下标。
  • JS的数组本质上还是一个对象,按照对象的定义,其索引就是属性名,只不过是非负整数值的字符串,且可以省略命名罢了。
  • JS对于索引与其他属性有不同的行为,对索引属性操作,会自动更新length属性。
  • 在V8引擎的源码中,jsArray继承自jsObject,二者的查找算法没有什么不同。
  • JS数组的值允许任意类型,同一数组也允许任意不同类型的值。
  • JS数组的索引最小是0, 最大可能的索引是(2^32)-2。
  • JS数组是动态的,不必在定义时为其指定元素和长度,可以动态地添加或移除元素,长度也会自动变化。
  • JS的数组可以是稀疏的,即其属性的索引允许是不连续、中间有空缺的。此时数组的长度是大于元素个数的,即数组的length属性的计算是将空缺也计入的。

创建数组对象与读写数组元素

可以使用工厂方法、构造函数或字面量的方式创建一个数组对象。
构造函数接收可选的任意个数的参数,若只有一个数值参数,它将作为数组的长度;若只有一个非数值参数或多个任意参数,它们将作为新建数组对象的元素。

  // Array.of() 工厂方法接收任意数量的参数作为新建数组对象的元素
  let a = Array.of(1,2,3);
  
  // Array.from()工厂方法接收两个参数,第一个为可迭代对象或一个类数组对象,第二个是一个可选的用于处理第一个对象的每个元素的函数,其返回值将作为新生产的数组的元素。
  let aLike = {0: 1, 1:2, length:2},
      // 一个可迭代的迭代器
      iterator = {
        position: 1,
        max: 10,
        next() {
          return this.position<=this.max ? {value: this.position++} : {done: true};
        },
        [Symbol.iterator]() {
          return this;
        },
      }.
    ;
  let a = Array.from();
      a = Array.from(iterator), // [1, ..., 10]
      a = new Array(),
      a = Array(1,2,3), // Array()构造函数允许函数式调用
      a = [4,5,6]; // 字面量

  coonsole.log(a[0]); // 4
  a[0] = -1; // 重新赋值
  a[100]; // 不存在的元素返回undefined

稀疏数组

当一个数组的元素变得不连续的时候,该数组是稀疏的。空缺的地方没有值,即使访问不存在的索引会返回undefined。
得到一个稀疏数组有许多方式,比如创建一个指定长度的空白数组、使用delete删除某个索引指向的元素是索引变得不再连续,给比数组最大索引还有大1或更多的属性赋值。

 let a = Array(3);
 a = [1,2,3]; delete  a[1];
 a = []; a[100] = 100;

添加与删除数组元素

  let a = [];
  a[0] = 0; // 使用中括号为其索引复制
  a.push(1); // 使用push追加元素

数组的迭代

 // 老派的for循环
 let a = [1,2,3];
 for (let i=0;i<a.length;i++) {
   console.log(a[i]);
 }
 // for/of循环
 for (const v of a) {
   console.log(v);
 }

 // 数组原型的forEach方法(Array.prototype.forEach)
 a.forEach((v, k) => {
   console.log(v, k);
 });

数组方法

Array.prototype.forEach()

元素遍历方法。
该方法接收一个处理器回调函数,该函数会被传入两个参数:元素的值和索引。
该方法是一个普通的函数,没有提供终止机制,如for循环那样的break/continue/return。
如果需要终止循环,可以在回调函数中抛出异常,并在forEach()方法调用处捕获。

Array.prototype.map()

元素变换方法。
map() 方法接收一个处理器回调函数,创建并返回一个新数组。它将原数组的每个元素作为参数去调用回调函数,并将函数返回值作为新数组的元素。

Array.prototype.filter()

元素过滤方法。
该方法接收一个用于过滤的回调函数,并返回一个新的数组。它将原数组的每个元素作为参数去调用回调函数,返回值为true便将该元素添加到创建的新数组。

Array.prototype.find() | findIndex() | findLast() | findLastIndex()

find()方法接收一个用于测试的回调函数,返回数组中第一个通过测试的元素的值。否则返回 undefined。
findIndex() 方法与find()方法用法一样,但返回元素的索引。

Array.prototype.every() / Array.prototype.some()

它们是数组的断言方法。
every()接收一个断言回调函数,当所有值都使断言函数返回true才返回true,否则返回false。
some() 用法与 every()一致,但它在有任何一个值是断言函数返回true就返回true。

Array.prototype.reduce() / Array.prototype.reduceRight()

reduce()方法接收两个参数:计算回调函数与第二个可选的初始值。
它正向遍历数组,访问第一个元素时,将初始值与第一个元素传入回调函数,若初始值未定义,传入数组的头一个元素值与第二个元素;
而后的遍历将每个数组元素经过回调函数计算后的结果与下一个要计算的元素值传入回调函数再次计算,并将最后一次计算的结果返回。

reduceRight()逆向遍历数组,它从数组的最后一个元素向前访问。

在其他编程语言中,这种设计的函数也会被叫做fold。而有的语言,reduce与fold同时存在,比如Kotlin的fold方法与js的reduce方法一样,而其reduce方法定义则减去了初始值的参数。

Array.prototype.flat() | Array.prototype.flatMap()

flat() 方法接收一个可选的数值参数,并创建一个新的数组返回。它会按照参数指定的深度递归地将子数组的元素添加到新数组,就是说将数组从嵌套结构展开,称作打平数组。如果不提供参数,默认展开一级嵌套。
flatMap() 的用法与map()一样,但是它会将回调函数返回的数组结果一级展开。使用array.map().flat()也能达到目的,但是这会增加遍历次数,降低效率。

Array.prototype.concat()

合并数组的方法。
该方法接收任意数量的参数,并创建一个新数组作为返回值。非数组类型的参数会被作为元素添加到新的数组,数组类型的参数会被一级展开,展开后的元素会被添加到新数组。

[].concat(1, [2, [3]], {0: 4, length: 1}); // [1, 2, [3], {0: 4, length: 1}]

Array.prototype.push()

向数组追加元素,返回数组的长度。它会修改调用它的数组。它不会展开收到的数组参数。

Array.prototype.pop()

删除数组最后一个元素,并返回被删除的元素值。它会修改调用者。
使用push()与pop()方法可以实现栈。栈是一种只允许在固定的一端进行插入与删除元素操作的线性表,其特点是先进后出。

Array.prototype.unshift() | Array.prototype.shift()

unshift()在数组开头插入元素,shift()在数组开头移除元素,它们都会修改调用者。
unshift()方法若有多个参数会将其一次性插入,意味着多次插入与一次插入数据,有着不同的结果:

let a = []; 
a.unshift(1);a.unshift(2);a.unshift(3);console.log(a); // [3,2,1]
a = [];a.unshift(1,2,3); console.log(a); // [1,2,3]

Array.prototype.slice()

slice()方法会切割数组。它接收两个参数,表示开始位置与结束位置,并返回从包含开始位置到结束位置之前的所有元素的新数组。这是一个左闭区间:[start, end)。

Array.prototype.splice() | Array.prototype.toSpliced()

在MDN,splice()方法被描述为"splice() 方法通过移除或者替换已存在的元素和/或添加新元素就地改变一个数组的内容"。
这比较抽象,要想理解它,最好在现实里找到映射。
splice翻译成汉语是“拼接”、“粘接”等意思,也就是吧几个同类的东西接到一起,比如绳子、管子等。

比如我们接水管:
若水管没问题,我们可以直接将另外一节或几节水管接到当前的水管上;
若一头坏掉了,我们可以锯掉一端,然后再接;
若是中间坏掉了,我们可以锯掉中间的一段,再接一段来换掉这个坏掉的

这个例子就完整地与splice()方法的功能对应起来了。

toSpliced() 方法不会修改原数组,返回一个拼接好的新数组

let a = [0, 1, 2];
// 添加新的元素,好比接水管
a.splice(3, 0, 3, 4, 5); // [0,1,2,3,4,5]

// 替换元素(从第三个索引开始,替换两个) 
// 好比替换掉水管中间坏掉的部分
a.splice(3, 2, '三', '四'); // [0, 1, 2, '三', '四', 5]

// 替换并添加 (从第三个索引开始,替换两个, 其余的参数追加到替换的元素后面)
// 好比替换掉水管中间坏掉的部分并加长
a.splice(3, 2, 3, 4, 'a', 'b', 'c'); // [0, 1, 2, 3, 4, 'a', 'b', 'c', 5]

Array.prototype.fill(value[, start[, end]])

用一个固定值填充一个数组中从起始索引到终止索引(但不包含)内的全部元素。
参数若未指定,起始默认0,终止默认数组长度。若指定的终止索引大于数组长度,并不会为数组新增元素。

let a = new Array(2);
a.fill('a', 0, 2); // ['a', 'a']
a.fill('b', 0, 10); // ['a', 'b']

Array.prototype.copyWithin(target[, start[, end]])

该方法将数组的一部分复制然后覆盖到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

let a = [0,1,2,3,4,5];
a.copyWithin(0, 3, 4); // [3,1,2,3,4,5]
a[0] = 0;
// 若参数为负数,则从数组末尾开始计算,即 a.length+target
a.copyWithin(-2, 3, 4); //  [1, 1, 2, 3, 3, 5]
a[4] = 4;
// 若复制的元素到最后一个元素为止,不会扩展数组
a.copyWithin(5, 0, 5); // [1, 1, 2, 3, 3, 1]

Array.prototype.indexOf() | Array.prototype.lastIndexOf()

返回数组中第一次/最后一次出现给定元素的下标,如果不存在则返回 -1。

Array.prototype.includes()

判断一个数组是否包含一个指定的值。可以判断是否存在NaN。

Array.prototype.with(index, value)

返回一个新的数组,其中 index 索引处的元素被替换为 value。

Array.prototype.sort() | Array.prototype.toSorted()

排序方法。
sort() 默认按照字母顺序的升序对元素排序,如果元素不是然字符串,会将其转为字符串再排序。未定义的元素会被排到最后。
它接收一个可选的回调函数参数,并传入要比较的两个值,这两个值都不会是undefined。
这个函数的返回值应该是一个数值,其正负性表示两个元素的前后顺序:

  • > 0 , 意味这a比b大,将a排在b后面
  • < 0 , 意味这a比b小,将a排在b后面
  • ===0 , 意味着二者一样,就保持二者原来的相对顺序

sort() 方法会修改调用它的数组并返回它,使用toSorted()可以不修改原数组,返回一个已排序的新数组。

Array.prototype.reverse() | Array.prototype.toReversed()

反转方法
reverse() 将数组元素反转,它会修改调用者,返回调用者。
toReversed() 不修改原数组,返回一个反转的新数组。

Array.prototype.join()

将数组转换为字符串,其元素值按照参数指定字符串分隔。它执行的是String.split()的反向操作。

  // 没有参数的调用与Array.prototype.toString()的结果一样
  [1,2,3].join(); // '1,2,3'

  [1,2,3].join(' '); // '1 2 3'
  [1,2,3].join('-'); // '1-2-3'
  new Array(6).join('.'); // ......

Array.isArray()

检查某个值是否数组类型

Array.of() | Array.from()

工厂方法,用于创建新的数组

类数组对象

类数组对象指的是有length属性,length值为不大于2^32的非负整数,且拥有属性名为数值的属性的对象。

  function isArrayLike (o) {
    return o &&
           typeof o === 'object' &&
           Number.idFinite(o.length) &&
           o.length >= 0 &&
           Number.isInteger(o.length) &&
           o.length <= 4294967294; // (2^32)-2
  }

将字符串对象作为数组使用

字符串对象就是一个类数组对象,只不过它是只读的。

'apple'[a]; // 'a'
posted @ 2023-05-18 20:21  钰琪  阅读(24)  评论(0编辑  收藏  举报