集合引用类型

Object

显示的创建 Object 实例有两种方式:

  1. new 操作符后跟 Object 构造函数
let person = new Object()
person.name = 'Nicholas'
person.age = 29
  1. 使用对象字面量
let person = {
	name: 'Nicholas',
	age: 29,
	5: true, // 数值属性会自动转换成字符串
}

在这个例子中,左大括号({)表示对象字面量开始,因为它出现在一个**表达式上下文(expression context)**中。在 ECMAScript 中,表达式上下文是指期待返回值的上下文。

同样是左大括号({),如果出现在**语句上下文(statement context)**中,比如 if 语句的条件后面,则表示一个语句块的开始

注意:在使用字面量表示法定义对象时,并不会实际调用 Object 构造函数

存取属性的两种方式:

  1. 点语法

  2. 中括号

从功能上讲,这两种存取属性的方式没有区别

使用中括号的主要优势:

  • 可以通过变量来访问属性
  • 属性名(可以包含非字母数字字符)中包含会导致语法错误的字符时,必须使用中括号语法

Array

创建数组

有两种基本的方式可以创建数组:

  1. 使用 Array 构造函数
  • 传入一个数值,则 length 属性会被自动创建并设置为这个值
    • let colors = new Array(20)
  • 传入要保存的元素
    • let colors = new Array('red', 'blue', 'green')

创建数组时给构造函数传入一个值。如果是数值,则会创建一个长度为指定数值的数组;如果是其他类型,则会创建包含那个值的数组

在使用 Array 构造函数时,也可以省略 new 操作符,结果是一样的

  1. 使用数组字面量表示法

与对象一样,在使用数组字面量表示法创建数组时,不会调用 Array 构造函数

Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:

  1. from(),用于将类数组结构转换为数组实例

  2. of(),用于将一组参数转换为数组实例

数组空位

使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)

const options = [, , , , ,]
console.log(options.length) // 5
console.log(options, options[0]) // [empty × 5] undefined

注:ES6 新增的方法普遍将这些空位当成存在的元素;而 ES6 之前的方法则会忽略这个空位,但具体的行为也会因方法而异。因此实践中要避免使用数组空位,可以显式地使用 undefined 值代替

数组索引

let colors = ['red', 'blue', 'green']
console.log(colors[0]) // red
colors[2] = 'black'
colors[3] = 'brown'

在中括号提供索引,表示要访问的值。如果把一个值设置给超过数组最大索引的索引,则数组长度会自动扩展到该索引值 + 1

数组中元素的数量保存在 length(>= 0) 中,它不是只读的。可以通过修改 length 属性从数组末尾删除或者添加元素

let colors = ['red', 'blue', 'green']
colors.length = 2
console.log(colors[2]) // undefined

colors.length = 4 // 新添加的值都将以 undefined 填充
console.log(colors[3]) // undefined

使用 length 属性可以方便地向数组末尾添加元素

let colors = ['red', 'blue', 'green']
colors[colors.length] = 'black'
colors[colors.length] = 'brown'

检测数组

如果判断一个对象是不是数组?

在只有一个网页(因为只有一个全局作用域)的情况下,使用 instanceof 操作符足矣:

if (value instanceof Array) {
	// do something
}

如果网页里有多个框架,则可能涉及两个不同的全局上下文,因此会有两个不同版本的 Array 构造函数。如果把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在另一个框架内本地创建的数组

为解决这个问题,ECMAScript 提供了 Array.isArray() 方法

if (Array.isArray(value)) {
	// do something
}

迭代器方法

ES6 中,Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values() 和 entries()

  • keys(),返回数组索引的迭代器
  • values(),返回数组元素的迭代器
  • entries(),返回数组索引/元素对的迭代器
const colors = ['red', 'blue', 'green']
const keys = colors.keys()
const values = colors.values()
const entries = colors.entries()
console.log(Array.isArray(keys)) // false

const keysArr = Array.from(keys)
const valuesArr = Array.from(values)
const entriesArr = Array.from(entries)

console.log(keysArr, typeof keysArr[0] === 'number') // [0, 1, 2] true
console.log(valuesArr) // ['red', 'blue', 'green']
console.log(entriesArr) // [[0, 'red'], [1, 'blue'], [2, 'green']]

复制和填充方法

ES6 新增了两个方法:批量复制方法 copyWithin() 和填充数组方法 fill()

  • fill(),向一个已有的数组中插入全部或部分相同的值

    • 开始索引用于指定开始填充的位置,默认为 0(负值索引从数组末尾开始计算,等价于数组长度加上负索引)
    • fill() 静默忽略超出数组边界、零长度及方向相反的索引范围
  • copyWithin() 按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引开始的位置(开始索引和结束索引同 fill())

转换方法

  • valueOf(),返回数组本身
  • toString(),返回由数组中每个值的等效字符串拼接而成的一个以逗号分隔的字符串(即数组的每个值都会调用 toString() 方法)
  • toLocaleString(),返回由数组中每个值的等效字符串拼接而成的一个以逗号分隔的字符串(即数组的每个值都会调用 toLocaleString() 方法)
  • join(),接收一个参数,即字符串分隔符

栈方法

  • push(),接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度
  • pop(),从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项

队列方法

  • shift(),移除数组中的第一个项并返回该项,同时将数组长度减 1
  • unshift(),在数组前端添加任意个项并返回新数组的长度

排序方法

  • reverse(),反转数组项的顺序
  • sort()
    • 默认按升序排列数组项,即最小的值位于最前面,最大的值排在最后面
    • sort() 会在每一项上调用 String(),然后比较字符串来决定顺序(即使数组的元素都是数值)
    const values = [0, 1, 5, 10, 15]
    values.sort()
    console.log(values) // [0, 1, 10, 15, 5]
    
    • sort() 方法可以接收一个比较函数,用于判断哪个值应该排在前面。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数

操作方法

  • concat(),基于当前数组中的所有项创建一个新数组

    • 首先创建一个当前数组的副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组
    • 如果参数不是数组,则直接把它们添加到结果数组末尾;如果传入一个或多个数组,则会把这些数组的每一项都添加到结果数组中(浅)
    const colors = ['red', 'green', 'blue']
    const colors2 = colors.concat('yellow', ['black', 'brown'])
    console.log(colors) // ['red', 'green', 'blue']
    console.log(colors2) // ['red', 'green', 'blue', 'yellow', 'black', 'brown']
    
    • 打平数组参数的行为可以重写,方法是在参数数组上指定一个特殊符号 Symbol.isConcatSpreadable。这个符号能够阻止 concat() 方法打平数组参数;相反,这个值设置为 true 可以强制打平类数组对象
    const colors = ['red', 'green', 'blue']
    const newColors = ['black', 'brown']
    let moreNewColors = {
    	[Symbol.isConcatSpreadable]: true,
    	length: 2,
    	0: 'pink',
    	1: 'cyan',
    }
    newColors[Symbol.isConcatSpreadable] = false
    
    // 强制不打平数组
    const colors2 = colors.concat('yellow', newColors)
    
    // 强制打平类数组对象
    const colors3 = colors.concat(moreNewColors)
    
    console.log(colors) // ['red', 'green', 'blue']
    console.log(colors2) // ['red', 'green', 'blue', 'yellow', Array(2)]
    console.log(colors3) // ['red', 'green', 'blue', 'pink', 'cyan']
    
  • slice(),用于创建一个包含原数组中部分项的新数组

    • 接收一或两个参数,即要返回项的起始和结束位置(不包含结束索引对应的元素)
    • 如果 slice() 的参数有负值,那么就用数组长度加上该值来确定相应的位置
    • slice() 方法不会影响原始数组
  • splice(),主要目的是在数组中间插入元素,但有 3 种不同的方式使用这个方法

    • 删除。传两个参数,要删除的第一项的位置和要删除的项数,比如 splice(0, 2)
    • 插入。传三个参数,起始位置、0(要删除的项数)和要插入的项,比如 splice(2, 0, 'red', 'green')
    • 替换。传三个参数,起始位置、要删除的项数和要插入的任意数量的项,比如 splice(2, 1, 'red', 'green')

搜素和位置方法

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

1. 严格相等(===)

以下三个方法接收两个参数:要查找的项和(可选的)表示查找起点位置的索引

  • indexOf()
  • lastIndexOf()
  • includes()

2. 断言函数

这两个方法也都接受第二个可选参数,用于指定断言函数内部 this 的值

  • find(),返回第一个匹配的元素
  • findIndex(),返回第一个匹配元素的索引

迭代方法

ECMAScript 为数组定义了 5 个迭代方法,每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值

  • every()
  • filter()
  • some()
  • forEach()
  • map()

归并方法

ECMAScript 为数组提供了两个归并方法:reduce() 和 reduceRight()

定型数组

定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率,它所指的其实是一种特殊的包含数值类型的数组

ArrayBuffer

Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位

ArrayBuffer() 是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间:

const buffer = new ArrayBuffer(16) // 在内存中分配 16 字节
console.log(buffer.byteLength) // 16

ArrayBuffer 一经创建就不能再调整大小。不过,可以使用 slice() 复制其全部或者部分到一个新的 ArrayBuffer 实例中:

const buf1 = new ArrayBuffer(16)
const buf2 = buf1.slice(4, 12)
console.log(buf2.byteLength) // 8

不能仅通过对 ArrayBuffer 的引用就读取或者写入内容,而是需要通过视图来实现。视图有不同的类型,但引用的都是 ArrayBuffer 中存储的二进制数据

ArrayBuffer 在分配失败时会抛出错误

ArrayBuffer 分配的内存不能超过 Number.MAX_SAFE_INTEGER(2^53 - 1)个字节

声明 ArrayBuffer 会将所有二进制初始化为 0

通过声明 ArrayBuffer 分配的堆内存可以被当成垃圾回收,不用手动释放

DataView

DataView 是允许读写 ArrayBuffer 的一种视图,专为文件 I/O 和网络 I/O 设计,其 API 支持对缓冲数据的高度控制,但相比于其他类型的视图性能差一些。DataView 对缓冲内容没有任何预设,也不能迭代

必须在对已有的 ArrayBuffer 读取或者写入时才能创建 DataView 实例:

const buf = new ArrayBuffer(16)

// DataView 默认使用整个 ArrayBuffer
const fullDataView = new DataView(buf)
console.log(fullDataView.byteOffset) // 0
console.log(fullDataView.byteLength) // 16
console.log(fullDataView.buffer === buf) // true

// 构造函数接收一个可选的字节偏移量和一个可选的字节长度
//   byteOffset=0 表示视图从缓冲起点开始
//   byteLength=8 表示限制视图为前 8 个字节
const firstHalfDataView = new DataView(buf, 0, 8)
console.log(firstHalfDataView.byteOffset) // 0
console.log(firstHalfDataView.byteLength) // 8
console.log(firstHalfDataView.buffer === buf) // true

// 如果不指定,则 DataView 会使用剩余的缓存
//   byteOffset=8 表示视图从缓冲的第 8 个字节开始
//   byteLength 未指定,默认为剩余缓冲
const secondHalfDataView = new DataView(buf, 8)
console.log(secondHalfDataView.byteOffset) // 8
console.log(secondHalfDataView.byteLength) // 8
console.log(secondHalfDataView.buffer === buf) // true

要通过 DataView 读取缓冲,还需要几个组件:

  • 要读或写的字节偏移量
  • 使用 ElementType 实现 JavaScript 的 Number 类型到缓冲内二进制格式的转换
  • 内存中值的字节序,默认为大端字节序(big-endian)

1. ElementType

DateView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个 ElementType,然后 DataView 会忠实地为读、写完成相应的转换

ECMAScript6 支持 8 种不同的 ElementType(见下表):

ElementType字节说明等价的 C 类型值的范围
Int818 位有符号整数signed char-128 ~ 127
Uint818 位无符号整数unsigned char0 ~ 255
Int16216 位有符号整数short-32768 ~ 32767
Uint16216 位无符号整数unsigned short0 ~ 65535
Int32432 位有符号整数int-2147483648 ~ 2147483647
Uint32432 位无符号整数unsigned int0 ~ 4294967295
Float32432 位浮点数float1.2e-38 ~ 3.4e38
Float64864 位浮点数double5.0e-324 ~ 1.8e308

DataView 为上表中的每种类型都暴露了 get 和 set 方法,这些方法使用 byteOffset 定位要读取或者写入值的位置。类型时可以互换使用的,如下例所示:

// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2)
const view = new DataView(buf)

// 说明整个缓冲确实所有二进制位都是 0
// 检查第一个和第二个字符
console.log(view.getInt8(0)) // 0
console.log(view.getInt8(1)) // 0
// 检查整个缓冲
console.log(view.getInt16(0)) // 0

// 将整个缓冲都设置为 1
// 255 的二进制表示为 11111111 (2^8 - 1)
view.setUint8(0, 255)

// DataView 会自动将数据转换为特定的 ElementType
// 255 的十六进制表示是 0xFF
view.setUint8(1, 0xff)

// 现在缓冲里都是 1 了
// 如果把它当作二补数的有符号整数,则应该是 -1
console.log(view.getInt16(0)) // -1

2. 字节序

“字节序”指的是计算系统维护的一种字节顺序的约定,DataView 只支持两种约定:大端字节序和小端字节序

  • 大端字节序(网络字节序):最高有效位保存在第一个字节,而最低有效位保存在最后一个字节
  • 小端字节序:最低有效位保存在第一个字节,而最高有效位保存在最后一个字节

JavaScript 运行时所在系统的原生字节序决定了如何读取或写入数据,但 DataView 并不遵守这个约定。对一段内存而言,DataView 是一个中立接口,它遵守指定的字节序。DataView 的所有 API 方法都以大端字节序作为默认值,但接收一个可选的布尔值参数,设置为 true 即可启用小端字节序

// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2)
const view = new DataView(buf)

// 填充缓冲,让第一位和最后一位都是 1
view.setUint8(0, 0x80) // 设置最左边的位等于 1(1000 0000)
view.setUint8(1, 0x01) // 设置最右边的位等于 1 (0000 0001)
// 则缓冲的内容为 1000 0000 0000 0001

// 按大端字节序读取 Unit16
// 0x80 是高字节,0x01 是低字节
// 0x8001 = 2^15 + 2^0 = 32769
console.log(view.getUint16(0)) // 32769

// 按小端字节序读取 Unit16
// 0x80 是低字节,0x01 是高字节
// 0x0180 = 2^7 + 2^8 = 384
console.log(view.getUint16(0, true)) // 384

// 按大端字节序写入 Uint16
view.setUint16(0, 0x0004)
// 缓冲内容:
// 0000 0000 0000 0100
console.log(view.getUint8(0)) // 0
console.log(view.getUint8(1)) // 4

// 按小端字节序写入 Uint16
view.setUint16(0, 0x0002, true)
// 缓冲内容:
// 0000 0010 0000 0000
console.log(view.getUint8(0)) // 2
console.log(view.getUint8(1)) // 0

3. 边界情形

DataView 完成读、写操作的前提是必须有充足的缓冲区,否则会抛出 RangeError 异常

const buf = new ArrayBuffer(6)
const view = new DataView(buf)

// 尝试读取部分超出缓冲范围的值
view.getInt32(4) // RangeError

// 尝试读取超出缓冲范围的值
view.getInt32(8) // RangeError
view.getInt32(-1) // RangeError

// 尝试写入超出缓冲范围的值
view.setInt32(4, 123) // RangeError

DataView 在写入缓冲里会尽最大努力把一个值转换为适当的类型,后备为 0。如果无法转换,则抛出 TypeError 异常

const buf = new ArrayBuffer(1)
const view = new DataView(buf)

view.setInt8(0, 1.5)
console.log(view.getInt8(0)) // 1

view.setInt8(0, [4])
console.log(view.getInt8(0)) // 4

view.setInt8(0, 'f')
console.log(view.getInt8(0)) // 0

view.setInt8(0, Symbol()) // TypeError

定型数组

定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近,但定型数组的区别在于,它特定于一种 ElementType 且遵循系统原生的字节序。相应地,定型数组提供了适用面更广的 API 和更高的性能

设计定型数组的目的是提高于 WebGL 等原生库交换二进制数据的效率

创建定型数组的方式包括:读取已有的缓冲、使用自由缓冲、填充可迭代结构,以及填充基于任意类型的定型数组。另外通过 <ElementType>.from()<ElementType>.of() 也可以创建定型数组

// 创建一个 12 字节的缓冲
const buf = new ArrayBuffer(12)
// 创建一个引用该缓冲的 Int32Array
const ints = new Int32Array(buf)
// 这个定型数组知道自己每个元素需要 4 字节
// 因此长度为 3
console.log(ints.length) // 3

// 创建一个长度为 6 的 Int32Array
const ints2 = new Int32Array(6)
// 每个数值使用 4 个字节,因此 ArrayBuffer 需要 24 字节
console.log(ints2.length) // 6
// 类似 DataView,定型数组也有一个指向关联缓冲的引用
console.log(ints2.buffer.byteLength) // 24

// 创建一个包含 [2, 4, 6, 8] 的 Int32Array
const ints3 = new Int32Array([2, 4, 6, 8])
console.log(ints3.length) // 4
console.log(ints3.buffer.byteLength) // 16
console.log(ints3[2]) // 6

// 通过复制 ints3 创建一个 Int16Array
const ints4 = new Int16Array(ints3)
// 这个新类型数组会分配自己的缓冲
// 对应索引的值会相应地转换为新格式
console.log(ints4.length) // 4
console.log(ints4.buffer.byteLength) // 8
console.log(ints4[2]) // 6

// 基于普通数组来创建一个 Int16Array
const ints5 = Int16Array.from([3, 5, 7, 9])
console.log(ints5.length) // 4
console.log(ints5.buffer.byteLength) // 8
console.log(ints5[2]) // 7

// 基于传入的参数创建一个 Float32Array
const floats = Float32Array.of(3.14, 2.718, 1.618)
console.log(floats.length) // 3
console.log(floats.buffer.byteLength) // 12
console.log(floats[2]) // 1.6180000305175781

定型数组的构造函数和实例都有一个 BYTES_PER_ELEMENT 属性,返回该类型数组中每个元素的大小:

console.log(Int16Array.BYTES_PER_ELEMENT) // 2
console.log(Int32Array.BYTES_PER_ELEMENT) // 4

const ints = new Int32Array(1),
	floats = new Float64Array(1)

console.log(ints.BYTES_PER_ELEMENT) // 4
console.log(floats.BYTES_PER_ELEMENT) // 8

如果定型数组没有用任何值初始化,则其关联的缓冲会以 0 填充:

const ints = new Int32Array(4)
console.log(ints[0]) // 0
console.log(ints[1]) // 0
console.log(ints[2]) // 0
console.log(ints[3]) // 0

1. 定型数组的行为

定型数组的行为与普通数组类似,但也有一些不同之处

不能在定型数组中使用的方法:

  • 合并、复制和修改定型数组,应当使用 set() 和 subarray() 方法
    • concat()
    • pop()
    • push()
    • shift()
    • splice()
    • unshift()

2. 上溢和下溢

// 长度为 2 有符号整数数组
// 每个索引保存一个二补数形式的有符号整数
// 范围是 -128 到 127
const ints = new Int8Array(2)

// 长度为 2 的无符号整数数组
// 每个索引保存一个无符号整数
// 范围是 0 到 255
const unsignedInts = new Uint8Array(2)

// 上溢的位不会影响相邻索引
// 索引只取最低有效位上的 8 位
unsignedInts[1] = 256
console.log(unsignedInts[1]) // [0, 0]
unsignedInts[1] = 511
console.log(unsignedInts[1]) // [0, 255]

// 下溢的位会被转换为无符号的等价值
// 0xFF 是以二补数形式表示的 -1(截取到 8 位)
// 但 255 是一个无符号整数
unsignedInts[1] = -1
console.log(unsignedInts[1]) // [0, 255]

// 上溢自动变成二补数形式
// 0x80 是无符号整数的 128,是二补数形式的 -128
ints[1] = 128
console.log(ints[1]) // [0, -128]

// 下溢自动变为二补数形式
// 0xFF 是无符号整数的 255,是二补数形式的 -1
ints[1] = 255
console.log(ints[1]) // [0, -1]

除了 8 种元素类型,还有一种“夹板”数组类型:Uint8ClampedArray,不允许任何方向溢出(除非真的做跟 canvas 相关的开发,否则不要使用它)。超出最大值 255 的值会被向下舍入为 255,低于最小值 0 的值会被向上舍入为 0

const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256])
console.log(clampedInts[0]) // [0, 0, 255, 255]

Map

作为 ECMAScript6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机制。Map 的大多数特性都可以通过 Object 来实现,但二者之间还是存在一些细微的差异:

  • Map 的键可以是任意值,包括函数、对象或任意基本类型
  • Map 实例会维护键值对的插入顺序
  • 从内存占用,插入性能,删除性能来看,Map 优于 Object
  • Map 和 Object 的查找速度差异较小

基本 API

创建和初始化:

// 使用嵌套数组初始化映射
const m1 = new Map([
	['key1', 'val1'],
	['key2', 'val2'],
	['key3', 'val3'],
])
console.log(m1.size) // 3

// 使用自定义迭代器初始化映射
const m2 = new Map({
	[Symbol.iterator]: function* () {
		yield ['key1', 'val1']
		yield ['key2', 'val2']
		yield ['key3', 'val3']
	},
})
console.log(m2.size) // 3
console.log(m2.has(undefined)) // false
console.log(m2.get(undefined)) // undefined
  • set() 添加键值对
  • get() 和 has() 查询
  • delete() 和 clear() 删除值
  • size 属性返回键值对的数量

顺序与迭代

映射实例可以提供一个迭代器(Iterator),能以插入顺序生成 [key, value] 形式的数组。可以通过 entries() 方法(或者 Symbol.iterator 属性,它引用 entires())获取这个迭代器

因为 entires() 是默认迭代器,因此可以直接对映射实例使用扩展操作符,把映射转换为数组

  • forEach((value, key, map) => {}, opt_thisArg)
  • keys() 返回键的迭代器
  • values() 返回值的迭代器

WeakMap

WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集,但有一些重要的区别:

  • WeakMap 的键只能是 Object 或继承自 Object 的类型,否则会抛出 TypeError

基本 API

创建和初始化:

const key1 = { id: 1 },
	key2 = { id: 2 },
	key3 = { id: 3 }

// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
	[key1, 'val1'],
	[key2, 'val2'],
	[key3, 'val3'],
])
  • set() 添加键值对
  • get() 和 has() 进行查询
  • delete() 删除

弱键

WeakMap 的键是弱键,这意味着如果键不再被引用,它所对应的值也会被回收

不可迭代键

WeakMap 的键不可迭代,因此没有 entries()、keys() 和 values() 方法,也没有 forEach() 方法,同时也没有 clear() 方法

WeakMap 实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值

Set

Set 是 ECMAScript6 新增的集合类型,在很多方面都像是加强的 Map,因为它们的大多数 API 和行为都是共有的

基本 API

创建和初始化:

// 使用数组初始化集合
const s1 = new Set(['val1', 'val2', 'val3'])
console.log(s1.size)

// 使用自定义迭代器初始化集合
const s2 = new Set({
	[Symbol.iterator]: function* () {
		yield 'val1'
		yield 'val2'
		yield 'val3'
	},
})
console.log(s2.size)
  • add() 添加值
  • has() 查询
  • size 属性取得元素数量
  • delete()(会返回一个布尔值,表示集合种是否存在要删除的值) 和 clear() 删除元素

顺序与迭代

Set 会维护插入时的顺序,因此支持顺序迭代

集合实例可以提供一个迭代器,能以插入顺序生成集合内容的数组。可以通过 values() 方法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())获取这个迭代器

因为 values() 是默认迭代器,随意可以直接对集合实例使用扩展操作,把集合转换为数组

集合的 entires() 方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合种每个值的重复出现

  • forEach((value, dupValue, set) => {}, opt_thisArg)

WeakSet

WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集,但有一些重要的区别:

  • WeakSet 的值只能是 Object 或继承自 Object 的类型,否则会抛出 TypeError

基本 API

创建和初始化:

const val1 = { id: 1 },
	val2 = { id: 2 },
	val3 = { id: 3 }

// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3])
  • add() 添加值
  • has() 查询
  • delete() 删除

弱值

WeakSet 的值是弱值,这意味着如果值不再被引用,它就会被回收

const ws = new WeakSet()
ws.add({}) // 因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收

不可迭代值

WeakSet 的值不可迭代,因此没有 entries()、keys() 和 values() 方法,也没有 forEach() 方法,同时也没有 clear() 方法

迭代与扩展操作

ECMAScript6 新增的迭代器和扩展操作符对集合引用类型特别有用,有 4 种原生集合类型定义了默认迭代器:

  • Array
  • 所有定型数组
  • Map
  • Set

意味着上述所有类型都支持顺序迭代,都可以传入 for-of 循环,兼容扩展操作符...

Last Updated: