Set 数据结构
ES6 提供了新的数据结构 Set,注意不是数据类型,而是数据结构。它类似于数组,但是成员的值(可以是任何类型,无论是原始值或者是对象引用都行)都是唯一的,没有重复的值。
Set 中的特殊值
Set 数据结构存储的成员 值总是唯一的,所以Set数据结构在添加新成员时需要判断两个值是否恒等,有几个特殊值需要特殊对待:
1、+0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
2、undefined 与 undefined 是恒等的,所以不重复;
3、NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复(Set 内部认为两个NaN是相等的)。
4、两个数组或两个对象 不同引用 不恒等,即便两个数组或两个对象值完全相同也不恒等(不同引用 它们存储位置不一样),故能重复。
Set 数据结构基本用法
// Set本身是一个构造函数,用来生成 Set数据结构实例。 let testSet = new Set(); testSet.add(1); // Set(1) {1} 在数组Array时,用 unshift()或 push()进行值的添加;Set 稍有不同,它用更语义化的 add()进行添加 testSet.add(5); // Set(2) {1, 5} testSet.add(5); // Set(2) {1, 5} 这里体现了值的唯一性 testSet.add("testSet"); // Set(3) {1, 5, "testSet"} 这里体现了存储类型的多样性 testSet.add(true); // Set(4) {1, 5, "testSet", true} testSet.add(null); // Set(5) {1, 5, "testSet", true, null} testSet.add(undefined); // Set(6) {1, 5, "testSet", true, null, undefined} testSet.add(undefined); // Set(6) {1, 5, "testSet", true, null, undefined} undefined 跟 undefined恒等,不能重复,只能添加一个 testSet.add([1, 2, 3]); // Set(7) {1, 5, "testSet", true, null, undefined, [1,2,3]} testSet.add([1, 2, 3]); // Set(8) {1, 5, "testSet", true, null, undefined, [1,2,3]} 对象之间不同引用 不恒等,即使两个对象值完全相同,Set也能存储 testSet.add({a: 1, b: 2}); // Set(9) {1, 5, "testSet", true, null, undefined, [1,2,3], {a: 1, b: 2}} testSet.add({a: 1, b: 2}); // Set(10) {1, 5, "testSet", true, null, undefined, [1,2,3], {a: 1, b: 2}} 对象之间不同引用 不恒等,即使两个对象值完全相同,Set也能存储 console.log(testSet); // Set(8) {1, 5, "testSet", true, null, …} // Set和 Array的区别:Set不允许内部有重复的值,如果有,则只显示一个,相当于做了去重。虽然 Set很像数组,但不是数组。
// Set构造函数可以接受一个数组或字符串或具有 iterable接口的其他数据结构作为参数,用来初始化 Set数据结构。 // Array 转 Set let arrayToSet = new Set(["value1", "value2", "value3"]); console.log(arrayToSet); // Set(3) {"value1", "value2", "value3"} // String 转 Set let stringToSet = new Set('TestDemo'); console.log(stringToSet); // Set(7) {"T", "e", "s", "t", "D", "m", "0"} // Set 对象作用 // 去除数组重复成员 let deduplication = new Set([1, 2, 3, 4, 3, 2, '4']); // Set数据结构不会做数据类型转换,所以 4和 "4"是两个不同的值 console.log(deduplication); // Set(5) {1, 2, 3, 4, "4"} // 去除字符串重复字符 let deduplicationChar = new Set('TestTestTest'); console.log(deduplicationChar); // Set(4) {"T", "e", "s", "t"} // 注:join方法不能格式化 Set数据结构,同时 toString方法也不能将 Set数据结构转换成 String。 console.log(deduplicationChar.join()); // Uncaught TypeError: deduplicationChar.join is not a function console.log(deduplicationChar.toString()); // [object Set]
Set 实例的属性和方法
Set 实例具有以下原型属性:
1、Set.prototype.constructor:构造函数,即默认的 Set函数。
2、Set.prototype.size:返回 Set实例的成员数量总数。
Set 实例具有以下原型方法,主要分为:操作方法(用于操作数据) 和 遍历方法(用于遍历成员)。
1、Set.prototype.add(value):添加某个值,返回 Set实例本身。
2、Set.prototype.delete(value):删除当前 Set实例内某个值,返回一个布尔值,表示该值删除是否成功。
3、Set.prototype.has(value):返回一个布尔值,表示该值是否为当前 Set实例内成员。
4、Set.prototype.clear():清除所有成员(清空当前 Set实例),没有返回值。
5、Set.prototype.keys():返回键名的遍历器。
6、Set.prototype.values():返回键值的遍历器。
7、Set.prototype.entries():返回键值对的遍历器。
8、Set.prototype.forEach():遍历每个成员的回调函数,可以遍历当前 Set实例内每个成员。
Set 实例原型方法 - 操作方法:add(value)、delete(value)、has(value)、clear()
// add(value) let addSet = new Set([1,'Test']); addSet.add(NaN); console.log(addSet); // Set(3) {1, "Test", NaN} addSet.add(null).add(undefined); // add可以链式操作,连续添加 console.log(addSet); // Set(5) {1, "Test", NaN, null, undefined} // delete(value) let deleteState = addSet.delete(null); console.log(deleteState); // true console.log(addSet.delete(0)); // false console.log(addSet); // Set(4) {1, "Test", NaN, undefined} // has(value) let hasValue = addSet.has(NaN); console.log(hasValue); // true console.log(addSet.has(0)); // false // clear() let clearSet = addSet.clear(); console.log(clearSet); // clear方法没有返回值,所以是 undefined
Set 实例原型方法 - 遍历方法:keys()、values()、entries()、forEach()
// Set的遍历顺序就是值的添加顺序,所以 Set数据结构的顺序是固定的,因此这个特性可以用来保存 流程化操作的回调函数,在 Promise中非常有用,用 Set实例保存一个回调函数列表,Promise就可以按照 Set实例内成员排列顺序 进行顺序调用执行。 // 由于 Set数据结构(类似数组)没有键名,只有键值(或者说键名和键值是同一个值),所以 keys方法 和 values方法的行为完全一样。 let testSet = new Set([1, "Test", NaN]); // keys() for (let item of testSet.keys()) { console.log(item); } // 1 // Test // NaN // values() for (let item of testSet.values()) { console.log(item); } // 1 // Test // NaN // entries():entries方法返回的遍历器都是以数组的形式呈现,同时包括键名和键值(前是名后是值),只是两个成员完全相等。 for (let item of testSet.entries()) { console.log(item); } // [1, 1] // ["Test", "Test"] // [NaN, NaN] // Set 数据结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。这就意味着,values方法可以省略,直接用for...of循环遍历 Set实例。 for (let item of testSet) { console.log(item); } // 1 // Test // NaN // forEach():Set实例与数组一样,也拥有forEach方法,可用于对每个成员执行指定操作,没有返回值。 testSet.forEach(function (value, key) { console.log(key + ':' + value); }); // 1:1 // Test:Test // NaN:NaN // forEach方法的参数是一个处理函数,该函数的参数与数组的forEach一致,依次为键值、键名、集合本身(上例省略了该参数)。注意:Set数据结构的键名就是键值(两者完全相同),因此第一个参数和第二个参数的值永远是一样的。 // 另外,forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
WeakSet 数据结构
WeakSet 数据结构 与 Set 数据结构类似,也是不重复的值的集合,但它与 Set 有两个区别。
1、WeakSet 的成员只能是对象,而不能是其他类型的值。
2、WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于 WeakSet之中。
这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为 0,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,从而会导致内存无法释放,进而可能会引发内存泄漏。
WeakSet里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet适合存放临时对象,以及存放跟对象绑定的信息,只要这些对象在外部消失,它在 WeakSet里面的引用就会自动消失。
由于上面这个特点,WeakSet的成员是不适合引用的,因为它随时都可能消失。另外,由于 WeakSet内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数都不一样了,而垃圾回收机制何时运行又是不可预测的,因此 ES6规定 WeakSet不可遍历,这些特点同样适用于后面的 WeakMap结构。
// WeakSet本身也是一个构造函数,用来生成 WeakSet数据结构实例。WeakSet数据结构新建实例时,不允许直接传值初始化 let testWeakSet = new WeakSet({"a": 1, "b": 2}); // Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator)) let testWeakSet = new WeakSet(); testWeakSet.add({"a": 1, "b": 2}); console.log(testWeakSet); // WeakSet {{…}} let tempObj = {'ab': 11, 'bc': 22}; testWeakSet.add(tempObj); testWeakSet.add(tempObj); console.log(testWeakSet); // WeakSet {{…}, {…}} WeakSet数据结构内的成员也不允许重复 testWeakSet.add(1); // Uncaught TypeError: Invalid value used in weak set WeakSet的成员只能是对象,不能是其他类型的值
WeakSet实例只有三个原型方法:add(value)、delete(value)、has(value),功能和 Set数据结构一样;同时 WeakSet没有size属性,故而无法遍历它的成员,也就没有keys()、values()、entries()、forEach()等遍历操作。
WeakSet成员不能遍历,是因为成员都是弱引用,随时都可能消失,遍历机制无法保证成员的存在,很可能刚遍历结束,成员就不存在了。WeakSet的一个用处,是储存 DOM节点,而不用担心这些节点从文档内移除后,引发内存泄漏。
let testWeakSet = new WeakSet(); testWeakSet.add({"a": 1, "b": 2}); testWeakSet.add({'ab': 11, 'bc': 22}); console.log(testWeakSet.size); // undefined console.log(testWeakSet.forEach); // undefined testWeakSet.forEach(function (value, key) { // Uncaught TypeError: testWeakSet.forEach is not a function console.log(key + ':' + value); });
总结:在实际开发过程中 Set数据结构用的比较多,WeakSet用的并不多,甚至很少出现,但它对传入值必须是对象作了很好的判断,所以灵活应用还是有一定的用处。