BetaThis is a live doc! Anyone with edit access can make updates in real time without having to publish.
Array & TypedArray

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray

构造函数

Array

  • 直接字面量 [xxx]

  • new Array(length) 利用长度构造

    • 注意 length 必须是非负整数,否则会报 RangeError: Invalid array length

  • Array.of(element1, element2, /* ..., */ elementN)利用元素构造

    • 注:其实也可以 new Array(element1, element2, ...) 但是这样会在「只有一个元素 + 这个元素是数字」的情况下出现问题 —— 会被当成 new Array(length)而非 new Array(element1),因此不推荐这样用

  • Array.from(iter)利用可迭代对象 / array-like 元素构造

另外,对于 Array 而言,Array(...) 的行为与 new Array(...)是相同的(但始终建议带上 new

稀疏数组

利用 new Array(length) 构造出来的数组是稀疏数组 —— 中间的元素是不存在的(empty )而不是 undefined

另外如下情形也会产生稀疏数组

  • 利用字面量构造时直接跳过元素 [1, 2, , , 5](没错这竟然不是语法错误而是个 feature。。)

    • 尾随逗号是被忽略的,即 [1, 2, ] 是一个两元素的非稀疏数组

      • 但是尾随多个逗号只有最后一个被忽略,即可 [1, 2, ,] 是一个三元素的稀疏数组(最后一个元素为 empty

  • 直接修改 length 为比原来大的值,多余的部分为 empty

    • 同时,直接为更大的不存在的下标赋值也会 —— 这个行为会扩大 length

  • 利用 delete 删除某个元素会直接删除这个元素 —— 而不是赋值为 undefined

1// Array constructor: 2const a = Array(5); // [ <5 empty items> ] 3 4// Consecutive commas in array literal: 5const b = [1, 2, , , 5]; // [ 1, 2, <2 empty items>, 5 ] 6 7// Directly setting a slot with index greater than array.length: 8const c = [1, 2]; 9c[4] = 5; // [ 1, 2, <2 empty items>, 5 ] 10c.length; // 5 11 12// Elongating an array by directly setting .length: 13const d = [1, 2]; 14d.length = 5; // [ 1, 2, <3 empty items> ] 15 16// Deleting an element: 17const e = [1, 2, 3, 4, 5]; 18delete e[2]; // [ 1, 2, <1 empty item>, 4, 5 ]

而对于 empty 项,有如下性质

  • 在以下情况下会视为元素为元素存在( undefined

    • 直接取值 (arr[index])

    • for...of

    • spreading ([...arr])

    • Array.prototype.keys() Array.prototype.values() Array.prototype.entries()

    • Array.prototype.toString()(不存在的元素和 undefined 一样被展示为空字符串)

  • 而在以下情况下会视为元素不存在(直接跳过)

    • forEach map filter some 等方法

    • for...in

    • {...arr} 会直接跳过这些不存在的元素

      • {...[1,undefined,3]} 会返回 {0: 1, 1: undefined, 2: 3}

      • {...[1,,3]} 会返回 {0: 1, 2: 3}

  • Methods that have special treatment for empty slots include the following: concat(), copyWithin(), every(), filter(), flat(), flatMap(), forEach(), indexOf(), lastIndexOf(), map(), reduce(), reduceRight(), reverse(), slice(), some(), sort(), and splice(). Iteration methods such as forEach don't visit empty slots at all. Other methods, such as concat, copyWithin, etc., preserve empty slots when doing the copying, so in the end the array is still sparse.

  • Newer methods (e.g. keys) do not treat empty slots specially and treat them as if they contain undefined. Methods that conflate empty slots with undefined elements include the following: entries(), fill(), find(), findIndex(), findLast(), findLastIndex(), includes(), join(), keys(), toLocaleString(), toReversed(), toSorted(), toSpliced(), values(), and with().

一个相关的问题:想创建一个 [0, 1, 2, ..., n-1] 的数组,new Array(n).map((_, index) => index) 是无效的(最终结果是一个 n 长的全 empty 数组),而需要明确使用 Array.from(new Array(n)).map((_, index) => index) 先将 new 出来的数组转换为非稀疏数组再 map

TypedArray

首先,TypedArray 是一个 abstract class,但是在 JS 中不直接存在(虽然可以通过 Uint8Array.prototype / Object.getPrototypeOf(Uint8Array) 等方法间接访问到,但我们应该也用不到它),我们通常使用的是其子类

这些子类除了内含的数据类型不同外行为基本相同,本文利用 TypedArray 名词做讲解、利用 Int8Array 做代码示例

可以利用如下方式来构造 TypedArray

  • new TypedArray(length) 利用长度构造

    • 如果 length 为负数,会报 RangeError: Invalid array length

    • 如果 length 为非负小数,会舍弃小数部分

  • TypedArray.from(anotherTypedArray) / TypedArray.from(iter) 利用另一个 TypedArray / 可迭代对象 / array-like 元素构造

    • 其实也可以 new TypedArray(anotherTypedArray/iter)

    • 会完全拷贝另一个数组 / 可迭代对象的元素

  • TypedArray.of(element1, element2, /* ..., */ elementN)利用元素构造

    • 与 Array 不同,TypedArray.of 不能用 new TypedArray 替代

  • new TypedArray(buffer) 利用 ArrayBuffer 进行构造(本文不涉及 ArrayBuffer 的内容,会另写文章)

    • 注意,这样构造不会拷贝 buffer 的元素,而是做一个引用关联

Array 不同,new TypedArray(...)new 是不可省略的

另外,TypedArray 不存在稀疏数组 —— 所有初始元素都是 0

  • 特别注意:delete 操作于 TypedArray 是 NOP

基本操作

判断是否是 Array / TypedArray

判断是否是 Array:Array.isArray

判断是否是 TypedArray:arr instanceof Uint8Array

map

特别注意 map 不会改变类型,对于 Array 而言无所谓,但是对于 TypedArray 一定注意 map 以后还是相同的 TypedArray

例如 Int8Array.of(32, 64).map(v => v * 2) 的结果是 Int8Array(2) [64, -128]

with

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/with

with 提供了一种方法来「设置某值」返回新的数组 —— 采用拷贝的形式,不会改动原数组

slice / subarray

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice

slice 返回原数组一定范围内的浅拷贝,对新数组的修改不会影响原数组

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray

subarray 是 TypedArray 独有的,返回的新数组与原数组共享同一个底层 buffer,对新数组的修改会影响到原数组

特殊操作

直接修改 length

只有 Array 可以修改长度,TypedArray 长度是定义后不可修改*的

*当 TypedArray 长度和底层 ArrayBuffer 关联时,TypedArray 长度可能会被动修改 —— 但是无论什么情况,无法直接修改 TypedArray 的长度

这里说的「直接修改 length」指的是直接修改 arr.length 的值而不使用 push pop 等方法

  • length 扩大

    • 数组长度扩大

    • 新增的长度部分的元素被赋值为 empty

  • length 缩小

    • 数组长度减小

    • 减少的长度部分的元素被删除

读写不存在的下标

这里「不存在的下标」特指「>= 数组长度的下标」,字符串和负数下标不在考虑范围内

  • Array

    • 读:返回 undefined

    • 写:扩大数组长度(行为同上述「直接修改 length」)

  • TypedArray

    • 读:返回 undefined

    • 写:NOP

字符串/负数/小数下标

JS 中所有 property 都是字符串,所以「数字」和「是数字的字符串」等价,因此这里讨论的应该是「负数字符串下标、小数字符串下标和非数字字符串下标」

Array

  • 会被当成普通 property

  • 调用 Array.prototype.toString 时不会展示

TypedArray

  • 非数字字符串下标会被当成普通 property,负数、小数下标会被忽略

    • 忽略的含义:读始终返回 undefined,写为 NOP

    • 注意,长得和负数一样的字符串下标也会被忽略,例如 "-1"

  • 调用 TypedArray.prototype.toString 时不会展示