# 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 有什么区别呢?
  • WeakMapMap 的区别:
    • WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为 key
    • WeakMap 的键名所指向的对象,不计入垃圾回收机制。
    • WeakMapkey 对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么 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)
})
Update on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

Nico Niconi WeChat Pay

WeChat Pay

Nico Niconi Alipay

Alipay