上个月过得比较混沌,忙起来的话就停不下来,周六也因此也来公司上班了。后面闲下来的时候只想休息,以至于到现在没有更新博客也将近一个月。上个月其实是更了两篇博客的,还有一篇的内容是作为之前博客的补充了(之前写得太水了)。正好现在这段时间闲下来了,为了缓解这种反差感,今天就摸一篇比较简单的博客找找感觉吧。
这次博客的内容实际上也是在疫情隔离期间看的课程了,可见这两个月的时间我再上海基本没有什么新知识的输入,光输出那段时间的课程笔记就够我摸个大半年的了。这样不是很好。这次博客的主题是 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 呢?这段时间空闲时间比较多,希望到时候多来几篇吧。