上个月过得比较混沌,忙起来的话就停不下来,周六也因此也来公司上班了。后面闲下来的时候只想休息,以至于到现在没有更新博客也将近一个月。上个月其实是更了两篇博客的,还有一篇的内容是作为之前博客的补充了(之前写得太水了)。正好现在这段时间闲下来了,为了缓解这种反差感,今天就摸一篇比较简单的博客找找感觉吧。

这次博客的内容实际上也是在疫情隔离期间看的课程了,可见这两个月的时间我再上海基本没有什么新知识的输入,光输出那段时间的课程笔记就够我摸个大半年的了。这样不是很好。这次博客的主题是 ES6 新增的一些数据类型,之前的博客里提过新增的基础类型 symbol,不怎么常用。这回的博客就来介绍几个其他的不怎么常用的对象类型。

封面

Set(集合)与数组去重

Set 对象允许你存储任何类型的唯一值,无论是原始值还是对象引用。下面是一个新建 Set 的例子:

const set1 = new Set([1, 2, 3, 4, 5]);
// Set(5) { 1, 2, 3, 4, 5 }

Set 构造函数接受一个可迭代对象,它的所有元素将不重复地被添加到新的 Set 中。如果不指定此参数或其值为 null,则返回的新的 Set 对象为空。需要注意的是,对象去重只是去重对象的引用,比如两个不同引用的空对象,是不会被去重的。

利用 Set 这个特性可以很方便地将数组去重。以往数组去重会采用遍历的方式:

function uniq(array) {
    const result = [];
    const hash = {};
    for(let i = 0; i < array.length; i++){
        hash[array[i]] = true;
    }
    for(let key in hash){
        result.push(key)
    }
    return result;
}

这样看上去可以实现数组去重,将数组的值都转为对象的键,利用对象的键不能重复的特性。但是这样做的缺点也很明显,因为在数组的值转为对象的键时,类型会被转为字符串。这样去重之后取得的数组将是字符串数组,所以这种去重方式只能用来去重字符串数组,功能有限。如果数组中含有对象,对象会被转为形如"[object Object]"的字符串。

如果使用 Set 去重就方便许多。Set 的值都是唯一的,再把 Set 的值转成数组,就可以得到去重的数组了:

function uniq(array){
    return Array.from(new Set(array));
}

Array.from会将试图将各种类型转换为数组,也是 ES6 新增的 API。

Set 示例还具有一些属性和方法:

  • size:返回 Set 对象的值的个数。
  • add(value):在 Set 对象尾部添加一个元素。返回该Set对象。
  • has(value):返回一个布尔值,表示该值在Set中存在与否。
  • delete(value):移除 Set 的中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回 true,否则返回 false)。Set.prototype.has(value)在此后会返回 false。
  • forEach(callbackFn[, thisArg]):按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。
  • clear():移除Set对象内的所有元素。

Map(映射)

Map 对象保存键值对,,并且能够记住键的原始插入顺序,任何类型值都可以作为键或值。

Map 实例可以由 set 方法设置Map对象中键的值,get 方法返回键对应的值:

const map = new Map();
const a = { name: 'Franko' };
map.set(a, 'hi');
// Map(1) {{…} => "hi"}
map.get(a)
// "hi"

Map 构造函数可以接受一个数组或者其他迭代器作为参数,其元素为键值对。因此数组的值须是两个元素的数组,例如: [[ 1, 'one' ],[ 2, 'two' ]]

new Map([[ 1, 'one' ],[ 2, 'two' ],[3,'three']])
// Map(3) {1 => "one", 2 => "two", 3 => "three"}

Map 弥补了对象不能设各种类型的值为键的缺点。Objects 和 Maps 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。不过 Maps 和 Objects 有一些重要的区别,在下列情况里使用 Map 会是更好的选择:

  • 一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。不过,虽然两个NaN并不相等,但作为 Map 的键时并没有区别。-0+0也被认为是相等的。
  • Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。
  • 你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。
  • Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
  • Map 在涉及频繁增删键值对的场景下会有些性能优势。

Map 实例是迭代器,可以使用 for...of 语句迭代,也可以使用 Map 实例的 forEach 迭代:

const myMap = new Map([[ 1, 'one' ],[ 2, 'two' ],[3,'three']]);
myMap.keys();
// MapIterator {1, 2, 3} 它按插入顺序包含了Map对象中每个元素的键
myMap.values();
// MapIterator {"one", "two", "three"} 它按插入顺序包含了Map对象中每个元素的值 
for(let [key, value] of myMap) {
    console.log(key, value);
}
myMap.forEach((value, key) => console.log(key, value), myMap)

WeakSet(弱集合)与 WeakMap(弱映射)

WeakSet 允许将若保持对象存储在对象中。看起来这句话完全没法理解,要想讲清楚就说来话长了。

就平时遇到的对象而言,可能会很大、很占内存。但是内存很贵,需要省着用,已经不余姚的内存需要及时清空,腾给替他程序用,这个逻辑就被成为垃圾回收。垃圾回收是复杂的算法,简而言之就是找出开发者访问不到的对象,并「杀死」它们。以回收绑定在 button 上的***为例,此时重新赋值 button 为 null,***就被回收了,无法再被访问到。

弱引用就是不属于垃圾回收的计算范围。在下面的例子中,此时obj变量引用的对象会被回收,即便set(弱)引用了这个对象:

const set = new WeakSet();
let obj = {};
set.add(obj);
o = null;

正因为如此,WeakSet 是不可枚举的,无法获取所有值。这也意味着 WeakSet 实例中没有存储当前对象的列表。WeakSet 比 Set 更适合(和执行)跟踪对象引用,尤其是在涉及大量对象时。

WeakSet 实例只具有三种方法:add()delete()has()

与 WeakSet 相似地,WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,但值可以是任意的。在 JavaScript 里,map API 可以通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。每次从该 map 取值的时候,都需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。

但这样的实现会有两个很大的缺点,首先赋值和搜索操作都是 O(n) 的时间复杂度( n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。

相比之下,原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。正由于这样的弱引用,WeakMap 的 key 是不可枚举的 (没有方法能给出所有的 key)。如果 key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map。


以上就是本篇博客中关于这几种数据类型的全部内容,虽然这些知识是「没什么用」,而且「很基础」的,但是感觉复习这么一下还是起到了扫盲的作用,因为感觉之前弱引用那里就忘差不多了,当时也没怎么听明白。今天把一片博客写完,实在是可喜可贺,虽然大部分内容也就是跟着 MDN 过了一遍,但是谁会这样过 MDN 呢?这段时间空闲时间比较多,希望到时候多来几篇吧。

划船