# Set
- 在 ES6 之前,我们存储数据的结构主要有两种:数组、对象。
- 在 ES6 中新增了另外两种数据结构:Set、Map,以及它们的另外形式 WeakSet、WeakMap。
- Set 是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
- 创建 Set 我们需要通过 Set 构造函数(暂时没有字面量创建的方式):
// 1. 生成 Set 结构 | |
const set = new Set() | |
set.add(1) | |
set.add(2) | |
set.add(3) | |
set.add(3) | |
set.add('4') | |
console.log(set) // Set(4) { 1, 2, 3, '4' } | |
// 2. 添加对象需注意 | |
set.add({}) | |
set.add({}) | |
console.log(set) // Set(6) { 1, 2, 3, '4', {}, {} } |
# 数组去重
- 我们可以发现 Set 中存放的元素是不会重复的,那么 Set 有一个非常常用的功能就是给数组去重。
- 我们之前的做法是通过 indexOf 判断是否存在该元素
const arr = [1, 2, 3, 2, 3, 4] | |
const newArr = [] | |
for (const k of arr) { | |
if (newArr.indexOf(k) === -1) { | |
newArr.push(k) | |
} | |
} | |
console.log(newArr) // [1, 2, 3, 4] |
- 而现在使用 Set 会非常方便做到这一点
const arr = [1, 2, 3, 2, 3, 4] | |
const newArr = Array.from(new Set(arr)) | |
//const newArr = [... new Set (arr)] // 这种方法也是可以的 | |
console.log(newArr) // [1, 2, 3, 4] |
# size 属性
- Set 属性:
- size 返回 Set 中元素的个数;
const set = new Set([1, 2, 3, 4]) | |
console.log(set.size) // 4 |
# Set 方法
-
Set 常用方法:
- add (value):添加某个元素,返回 Set 对象本身;
-
delete (value):从 set 中删除和这个值相等的元素,返回 boolean 类型;
- has (value):判断 set 中是否存在某个元素,返回 boolean 类型;
-
clear ():清空 set 中所有的元素,没有返回值;
const set = new Set([1, 2, 3, 4]) | |
set.add(5) | |
set.add(6) | |
console.log(set.size) // 6 | |
// 1.delete 传入的是需要删除的元素 不支持索引 | |
set.delete(5) | |
console.log(set.size) // 5 | |
// 2.has 判断是否包含元素 | |
console.log(set.has(1)) // true | |
console.log(set.has(10)) // false | |
// 3.clear 清除所有元素 | |
// set.clear() | |
// console.log(set.size) // 0 |
# Set 遍历
-
forEach (callback, [, thisArg]):通过 forEach 遍历 set;
-
Set 也是支持 for of 的遍历的
// 对 set 进行遍历 | |
const set = new Set([1, 2, 3, 4]) | |
// forEach | |
set.forEach(item => console.log(item)) // 1 2 3 4 | |
// for of | |
for (const item of set) { | |
console.log(item) // 1 2 3 4 | |
} |
# WeakSet
-
WeakSet 和 Set 区别
- 区别一:WeakSet 中只能存放对象类型,不能存放基本数据类型;
-
区别二:WeakSet 对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么 GC 可以对该对象进行回收;
-
WeakSet 是一个构造函数,可以使用
new
命令,创建 WeakSet 数据结构。
const weakSet = new WeakSet() // WeakSet {} |
- 首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
const weakSet = new WeakSet() | |
weakSet.add(1) // TypeError: Invalid value used in weak set |
- 作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。
const arr = [[1, 2], [3, 4]]; | |
const ws = new WeakSet(arr); // WeakSet {[1, 2], [3, 4]} |
- 上面代码中,
arr
是一个数组,它有两个成员,也都是数组。将arr
作为 WeakSet 构造函数的参数,arr
的成员会自动成为 WeakSet 的成员。 - 注意,是
arr
数组的成员成为 WeakSet 的成员,而不是arr
数组本身。这意味着,数组的成员只能是对象。
const arr = [1, 2, 3, 4]; | |
const ws = new WeakSet(arr); // TypeError: Invalid value used in weak set |
# WeakSet 方法
- WeakSet.prototype.add (value):向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete (value):清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has (value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
const ws = new WeakSet(); | |
const lain = {}; | |
const saber = {}; | |
// 1.add (value): 添加某个元素,返回 WeakSet 对象本身 | |
ws.add(lain) | |
ws.add(saber) | |
console.log(ws) // WeakSet {{…},{…}} | |
// 2.has (value):判断 WeakSet 中是否存在某个元素,返回 boolean 类型 | |
console.log(ws.has(lain)) // true | |
console.log(ws.has(window)) // false | |
// 3.delete (value):从 WeakSet 中删除和这个值相等的元素,返回 boolean 类型 | |
ws.delete(saber) | |
console.log(ws.has(saber)); // false |
# 应用场景
const foo = new WeakSet() | |
class Characters { | |
constructor() { | |
foo.add(this) | |
} | |
sleeping () { | |
if (!foo.has(this)) { | |
throw new TypeError('Characters.prototype.sleeping 只能在Characters的实例上调用!'); | |
} | |
} | |
} | |
const lain = new Characters() | |
lain.sleeping() |
- 上面代码保证了
Characters
的实例方法,只能在Characters
的实例上调用。这里使用 WeakSet 的好处是,lain
对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑lain
,也不会出现内存泄漏。
# Map
- 另外一个新增的数据结构是 Map,用于存储映射关系。
- 但是在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
- 事实上我们对象存储映射关系只能用字符串(ES6 新增了 Symbol)作为属性名(key);
- 某些情况下我们可能希望通过其他类型作为 key,比如对象,这个时候会自动将对象转成字符串来作为 key;
- 之前对象是不能使用对象作为 key
const lain = { | |
name: 'lain' | |
} | |
const saber = { | |
name: 'saber' | |
} | |
const characters = { | |
[lain]: 'lain', | |
[saber]: 'saber' | |
} | |
console.log(characters) // {[object Object]: 'saber'} | |
// 对象作为 key 都会被转为 '[object Object]',所以只输出下面一个是因为下面的将上面覆盖了 |
- Map 则是允许我们对象类型来作为 key 的
const lain = {name: 'lain'} | |
const map = new Map() | |
map.set(lain, 'lain') | |
map.set(1, 1) | |
map.set('obj', () => {}) | |
map.set(null) | |
console.log(map) | |
// Map(4) {{…} => 'lain', 1 => 1, 'obj' => ƒ, null => undefined} |
# Map 传入 entries 格式
错误格式
const map = new Map([123]) | |
// Iterator value 123 is not an entry object |
- 正确传入格式: [[key, value], [key, value], [key, value]]
const map = new Map([['name', 'lain'], ['age', 16], [123, 123]]) | |
console.log(map) // Map(3) {'name' => 'lain', 'age' => 16, 123 => 123} |
# size
属性返回 Map 结构的成员总数。
const map = new Map([['name', 'lain'], ['age', 16], [123, 123]]) | |
console.log(map.size) // 3 |
# Map 方法
# Map.prototype.set(key, value)
set
方法设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。
const map = new Map([['lain', 15]]) | |
map.set('saber', 16) | |
console.log(map) // Map(2) {'lain' => 15, 'saber' => 16} |
set
方法返回的是当前的Map
对象,因此可以采用链式写法。
const map = new Map([['lain', 15]]) | |
.set('saber', 16) | |
.set('樱岛麻衣', 16) | |
console.log(map) // Map (3) {'lain' => 15, 'saber' => 16, ' 樱岛麻衣 ' => 16} |
# Map.prototype.get(key)
get
方法读取key
对应的键值,如果找不到key
,返回undefined
。
const map = new Map([['lain', 15]]) | |
// 传入 key 获取 value | |
console.log(map.get('lain')) // 15 | |
console.log(map.get(15)) // undefined |
# Map.prototype.has(key)
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
const map = new Map([['lain', 15]]) | |
console.log(map.has('lain')) // true | |
console.log(map.has('saber')) // false |
# Map.prototype.delete(key)
delete
方法删除某个键,返回true
。如果删除失败,返回false
。
const map = new Map([['lain', 15]]) | |
const res = map.delete('lain') | |
console.log(res) // true | |
console.log(map.size) // 0 |
# Map.prototype.clear()
clear
方法清除所有成员,没有返回值。
const map = new Map([['lain', 15]]) | |
.set('saber', 16) | |
.set('樱岛麻衣', 16) | |
const res = map.clear() | |
console.log(res) // undefined | |
console.log(map.size) // 0 |
# 遍历方法
- forEach
const map = new Map([['lain', 15]]) | |
.set('saber', 16) | |
.set('樱岛麻衣', 17) | |
map.forEach((value, key) => console.log(value, key)) // 15 'lain' 16'saber' 17 ' 樱岛麻衣' |
- for of
const map = new Map([['lain', 15]]) | |
.set('saber', 16) | |
.set('樱岛麻衣', 17) | |
for (const item of map) { | |
console.log(item) // ['lain', 15] ['saber', 16] [' 樱岛麻衣 ', 17] | |
} | |
for (const [key, value] of map) { | |
console.log(key, value) // lain 15 saber 16 樱岛麻衣 17 | |
} |
# WeakMap
- 和 Map 类型相似的另外一个数据结构称之为 WeakMap,也是以键值对的形式存在的。
- 那么和 Map 有什么区别呢?
WeakMap
和Map
的区别:WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为key
。WeakMap
的键名所指向的对象,不计入垃圾回收机制。WeakMap
的key
对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么 GC 可以回收该对象;
const map = new Map() | |
map.set('Lain', 16) | |
console.log(map) // Map(1) {'Lain' => 16} | |
const weakMap = new WeakMap() | |
weakMap.set('Lain',16) // TypeError: Invalid value used as weak map key |
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合。
// WeakMap 可以接受一个数组,作为构造函数的参数 | |
const arr1 = [1, 2, 3] | |
const arr2 = [4, 5, 6] | |
const wm = new WeakMap([[arr1, 'arr1'], [arr2, 'arr2']]) | |
console.log(wm.get(arr1)) // arr1 |
# WeakMap 方法
const lain = {name: 'lain'} | |
const saber = { name: 'saber' } | |
const weakMap = new WeakMap() | |
//set 方法 | |
weakMap.set(lain, 16) | |
weakMap.set(saber, 16) | |
//get 方法 | |
console.log(weakMap.get(lain)) // 16 | |
//has 方法 | |
console.log(weakMap.has(saber)) // true | |
console.log(weakMap.has(window)) // false | |
//delete 方法 | |
// 删除失败返回 false | |
console.log(weakMap.delete(1)) // false | |
// 删除成功返回 false | |
console.log(weakMap.delete(lain)) // true | |
console.log(weakMap.delete(saber)) // true | |
console.log(weakMap) // WeakMap {} | |
// WeakMap 不能遍历 | |
console.log(weakMap.forEach) // undefined |
# 应用场景 -> 响应式原理数据结构☟
//lain 对象 | |
const lain = { | |
name: 'lain', | |
age: 16 | |
} | |
//lain 相关方法 | |
function lainNameFn1() { | |
console.log('lain name 发生了改变,执行 lainNameFn1 函数'); | |
} | |
function lainNameFn2() { | |
console.log('lain name 发生了改变,执行 lainNameFn2 函数'); | |
} | |
function lainAgeFn1() { | |
console.log('lain age 发生了改变,执行 lainAgeFn1 函数'); | |
} | |
function lainAgeFn2() { | |
console.log('lain age 发生了改变,执行 lainAgeFn2 函数'); | |
} | |
//saber 对象 | |
const saber = { | |
name: 'saber', | |
age: 17 | |
} | |
//saber 相关方法 | |
function saberNameFn1() { | |
console.log('saber name 发生了改变,执行 saberNameFn1 函数'); | |
} | |
function saberNameFn2() { | |
console.log('saber name 发生了改变,执行 saberNameFn2 函数'); | |
} | |
function saberAgeFn1() { | |
console.log('saber age 发生了改变,执行 saberAgeFn1 函数'); | |
} | |
function saberAgeFn2() { | |
console.log('saber age 发生了改变,执行 saberAgeFn2 函数'); | |
} | |
// 创建 Map 对象 | |
const lainMap = new Map() | |
const saberMap = new Map() | |
// 将属性名设置为 key, 相关方法设置为 value | |
lainMap.set('name', [lainNameFn1, lainNameFn2]) | |
lainMap.set('age', [lainAgeFn1, lainAgeFn2]) | |
saberMap.set('name', [saberNameFn1, saberNameFn2]) | |
saberMap.set('age', [saberAgeFn1, saberAgeFn2]) | |
// 创建 WeakMap 对象 | |
// 因为如果有一天 lain 需要销毁,lain = null。使用 Map 会有依赖,不会被销毁。而 WeakMap 是弱引用,这正是它的价值所在~ | |
const weakMap = new WeakMap() | |
// 将 lain/saber 对象作为 key, lainMap/saberMap 作为 value | |
weakMap.set(lain, lainMap) | |
weakMap.set(saber, saberMap) | |
// 如果 lain 的 name 属性发生了改变 | |
lain.name = '樱岛麻衣' | |
const lainNamesFns = weakMap.get(lain).get('name') | |
const saberNamesFns = weakMap.get(saber).get('name') | |
new Promise(resolve => { | |
setTimeout(() => { | |
lainNamesFns.forEach(item => item()) | |
saber.name = '小鸟游六花' | |
resolve(saberNamesFns) | |
}, 1000) | |
}) | |
.then(saberNamesFns => { | |
setTimeout(() => saberNamesFns.forEach(item => item()), 1000) | |
}) |