6.6 Set
ECMAScript 6新增的Set是一种新集合类型,为这门语言带来集合数据结构。Set在很多方面都像是加强的Map,这是因为它们的大多数API和行为都是共有的。
6.6.1 基本API
使用new关键字和Set构造函数可以创建一个空集合:
const m = new Set();
如果想在创建的同时初始化实例,则可以给Set构造函数传入一个可迭代对象,其中需要包含插入到新集合实例中的元素:
初始化之后,可以使用add()增加值,使用has()查询,通过size取得元素数量,以及使用delete()和clear()删除元素:
add()返回集合的实例,所以可以将多个添加操作连缀起来,包括初始化:
与Map类似,Set可以包含任何JavaScript数据类型作为值。
集合也使用SameValueZero操作(ECMAScript内部定义,无法在语言中使用),基本上相当于使用严格对象相等的标准来检查值的匹配性。
与严格相等一样,用作值的对象和其他“集合”类型在自己的内容或属性被修改时也不会改变:
add()和delete()操作是幂等的。
delete()返回一个布尔值,表示集合中是否存在要删除的值:
6.6.2 顺序与迭代
Set会维护值插入时的顺序,因此支持按顺序迭代。
集合实例可以提供一个迭代器(Iterator),能以插入顺序生成集合内容。
可以通过values()方法及其别名方法keys()(或者Symbol.iterator属性,它引用values())取得这个迭代器:
因为values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组:
集合的entries()方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现:
如果不使用迭代器,而是使用回调方式,则可以调用集合的forEach()方法并传入回调,依次迭代每个键/值对。
传入的回调接收可选的第二个参数,这个参数用于重写回调内部this的值:
修改集合中值的属性不会影响其作为集合值的身份:
6.6.3 定义正式集合操作
从各方面来看,Set跟Map都很相似,只是API稍有调整。唯一需要强调的就是集合的API对自身的简单操作。
很多开发者都喜欢使用Set操作,但需要手动实现:或者是子类化Set,或者是定义一个实用函数库。
要把两种方式合二为一,可以在子类上实现静态方法,然后在实例方法中使用这些静态方法。
在实现这些操作时,需要考虑几个地方:
❑ 某些Set操作是有关联性的,因此最好让实现的方法能支持处理任意多个集合实例。
❑ Set保留插入顺序,所有方法返回的集合必须保证顺序。
❑ 尽可能高效地使用内存。扩展操作符的语法很简洁,但尽可能避免集合和数组间的相互转换能够节省对象初始化成本。
❑ 不要修改已有的集合实例。union(a, b)或a.union(b)应该返回包含结果的新集合实例。


6.7 WeakSet
ECMAScript 6新增的“弱集合”(WeakSet)是一种新的集合类型,为这门语言带来了集合数据结构。
WeakSet是Set的“兄弟”类型,其API也是Set的子集。
WeakSet中的“weak”(弱),描述的是JavaScript垃圾回收程序对待“弱集合”中值的方式。
6.7.1 基本API
可以使用new关键字实例化一个空的WeakSet:
const ws = new WeakSet();
弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError。
如果想在初始化时填充弱集合,则构造函数可以接收一个可迭代对象,其中需要包含有效的值。
可迭代对象中的每个值都会按照迭代顺序插入到新实例中:
初始化之后可以使用add()再添加新值,可以使用has()查询,还可以使用delete()删除
add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明
6.7.2 弱值
WeakSet中“weak”表示弱集合的值是“弱弱地拿着”的。
意思就是,这些值不属于正式的引用,不会阻止垃圾回收。
来看下面的例子:
const ws = new WeakSet();ws.add({});
add()方法初始化了一个新对象,并将它用作一个值。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收。然后,这个值就从弱集合中消失了,使其成为一个空集合。
再看一个稍微不同的例子:
这一次,container对象维护着一个对弱集合值的引用,因此这个对象值不会成为垃圾回收的目标。不过,如果调用了removeReference(),就会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清理掉。
6.7.3 不可迭代值
因为WeakSet中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力。
当然,也用不着像clear()这样一次性销毁所有值的方法。WeakSet确实没有这个方法。
因为不可能迭代,所以也不可能在不知道对象引用的情况下从弱集合中取得值。
即便代码可以访问WeakSet实例,也没办法看到其中的内容。
WeakSet之所以限制只能用对象作为值,是为了保证只有通过值对象的引用才能取得值。
如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
6.7.4 使用弱集合
相比于WeakMap实例,WeakSet实例的用处没有那么大。
弱集合在给对象打标签时还是有价值的。
来看下面的例子,这里使用了一个普通Set:
这样,通过查询元素在不在disabledElements中,就可以知道它是不是被禁用了。
不过,假如元素从DOM树中被删除了,它的引用却仍然保存在Set中,因此垃圾回收程序也不能回收它。
为了让垃圾回收程序回收元素的内存,可以在这里使用WeakSet:
这样,只要WeakSet中任何元素从DOM树中被删除,垃圾回收程序就可以忽略其存在,而立即释放其内存(假设没有其他地方引用这个对象)。
6.8 迭代与扩展操作
ECMAScript 6新增的迭代器和扩展操作符对集合引用类型特别有用。
这些新特性让集合类型之间相互操作、复制和修改变得异常方便。
有4种原生集合类型定义了默认迭代器:
❑ Array ❑ 所有定型数组 ❑ Map ❑ Set
很简单,这意味着上述所有类型都支持顺序迭代,都可以传入for-of循环:
这也意味着所有这些类型都兼容扩展操作符。
扩展操作符在对可迭代对象执行浅复制时特别有用,只需简单的语法就可以复制整个对象:
对于期待可迭代对象的构造函数,只要传入一个可迭代对象就可以实现复制:
也可以构建数组的部分元素:
浅复制意味着只会复制对象引用
上面的这些类型都支持多种构建方法,比如Array.of()和Array.from()静态方法。
在与扩展操作符一起使用时,可以非常方便地实现互操作:
6.9 小结
JavaScript中的对象是引用值,可以通过几种内置引用类型创建特定类型的对象。
❑ 引用类型与传统面向对象编程语言中的类相似,但实现不同。
❑ Object类型是一个基础类型,所有引用类型都从它继承了基本的行为。
❑ Array类型表示一组有序的值,并提供了操作和转换值的能力。
❑ 定型数组包含一套不同的引用类型,用于管理数值在内存中的类型。
❑ Date类型提供了关于日期和时间的信息,包括当前日期和时间以及计算。
❑ RegExp类型是ECMAScript支持的正则表达式的接口,提供了大多数基本正则表达式以及一些高级正则表达式的能力。
JavaScript比较独特的一点是,函数其实是Function类型的实例,这意味着函数也是对象。
由于函数是对象,因此也就具有能够增强自身行为的方法。
因为原始值包装类型的存在,所以JavaScript中的原始值可以拥有类似对象的行为。
有3种原始值包装类型:Boolean、Number和String。它们都具有如下特点:
❑ 每种包装类型都映射到同名的原始类型。
❑ 在以读模式访问原始值时,后台会实例化一个原始值包装对象,通过这个对象可以操作数据。
❑ 涉及原始值的语句只要一执行完毕,包装对象就会立即销毁。
JavaScript还有两个在一开始执行代码时就存在的内置对象:Global和Math。
其中,Global对象在大多数ECMAScript实现中无法直接访问。不过浏览器将Global实现为window对象。所有全局变量和函数都是Global对象的属性。
Math对象包含辅助完成复杂数学计算的属性和方法。
ECMAScript 6新增了一批引用类型:Map、WeakMap、Set和WeakSet。这些类型为组织应用程序数据和简化内存管理提供了新能力。
