《JavaScript 高级程序设计》第六章 集合引用类型 学习记录

412 阅读18分钟

1、Object

  • 创建方式
    • new操作符和Object构造函数
    • 对象字面量
  • 使用对象字面量表示法定义对象时,并不会调用Object构造函数
  • 中括号方式,可以传变量

2、Array

1、创建数组

  • Array构造函数 let colors = new Array()

  • 已知数量 let colors = new Array(20)

  • 传入元素let colors = new Array("red", "blue", "green")

  • 传入一个值

    • 数值则会创建数值长度的数组
    • 其他类型,创建一个只包含该值的数组
  • 省略new 操作符,结果相同

  • 数组字面量 let colors = [1,2]

    • 不会调用Array构造函数
  • Array.from()

    • 将类数组转为数组实例
    // ["M","a","t","t"]
    Array.from("Matt") 
    
    const m = new Map().set(1,2)
              .set(3,4)
    const s = new Set().add(1)
              .add(2)
              .add(3)
              .add(4)
    Array.from(m) // [[1,2],[3,4]]
    Array.from(s) // [1,2,3,4]
    
    • 对现有数组执行浅复制
    const a1 = [1,2,3,4];
    const a2 = Array.from(a1)
    a1        // [1,2,3,4]
    a1 === a2 // false
    
    • 可以使用任何可迭代对象
    const iter = {
      *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
      }
    }
    Array.from(iter) // [1,2,3,4]
    
    • arguments 对象可以转为数组
    function getArgsArray() {
      return Array.from(arguments)
    }
    getArgsArray(1,2,3,4) // [1,2,3,4]
    
    • from() 能转换带有必要属性的自定义对象
    const arrayLikeObj = {
      0: 1,
      1: 2,
      2: 3,
      3: 4,
      length: 4
    }
    Array.from(arrayLikeObj) //[1,2,3,4]
    
    • 第二个参数可选 映射函数参数

      • 直接增强新数组的值,无需Array.from().map()创建中间数组
    • 第三个参数,用于接收映射函数中this值

      • 箭头函数不适用
      const a1 = [1,2,3,4]
      const a2 = Array.from(a1, x=>x**2)
      const a3 = Array.from(a1, function(x){return x**this.exponent}, {exponent: 2})
      a2 // [1, 4, 9, 16]
      a3 // [1, 4, 9, 16]
      
  • Array.of()

    • 将一组参数转换为数组
    • 替代Array.prototype.slice.call(arguments)
    Array.of(1,2,3,4) // [1,2,3,4]
    Array.of(undefined) // [undefined]
    

2、数组空位

  • 字面量初始化时,可以用一串逗号创建空位
  • es6认为是undefined
  • 行为不一致
    • map 跳过空位置
    • join 视空位置为空字符串

3、数组索引

  • 如果把一个值设置给超过最大索引的索引,数组长度会自动扩展到该索引+1

  • length属性可以修改,用来删除或增加元素

    let c = [1,2,3]
    c[c.length] = 4
    c[c.length] = 5
    

4、检测数组

  • 判断一个对象是不是数组(只有一个全局作用域)
value instanceof Array
  • 如果有多个作用域
Array.isArray(value)

5、迭代器方法

  • keys()
    • 返回数组索引迭代器
  • values()
    • 返回数组元素迭代器
  • entries()
    • 返回索引/值对的迭代器
const a = ["foo", "bar", "baz"]

Array.from(a.keys())   // [0, 1, 2]
Array.from(a.values()) // ["foo", "bar", "baz"]
Array.from(a.entries()) // [[0, "foo"], [1, "bar"], [2, "baz"]]

for(const [idx, elem] of a.entries()) {
  alert(idx)
  alert(elem)
}

6、复制和填充方法

  • copyWithin()

    • 批量复制
    • 第一个参数开始填充的索引
    • 第二个参数开始复制的索引(默认0)
    • 第三个参数结束复制的索引(默认尾部)
    • 负索引从末尾算
    • 按照制定范围浅复制数组中的部分内容,将其插入到制定索引开始的位置。
    • 修改原数组
    let ints = [0,1,2,3,4,5,6,7,8,9]
    
    // 每次默认认为重置回[0,1,2,3,4,5,6,7,8,9]
    ints.copyWithin(5) //[0,1,2,3,4,0,1,2,3,4]
    ints.copyWithin(0,5)//[5,6,7,8,9,5,6,7,8,9]
    ints.copyWithin(4,0,3)//[0,1,2,3,0,1,2,7,8,9]
    ints.copyWithin(2,0,6)//[0,1,0,1,2,3,4,5,8,9]
    ints.copyWithin(-4,-7,-3)//[0,1,2,3,4,5,3,4,5,6]
    
    // 忽略超出数组边界,零长度及方向相反的索引范围
    // 索引过底忽略
    ints.copyWithin(1,-15,-12) //[0,1,2,3,4,5,6,7,8,9]
    // 索引过高忽略
    ints.copyWithin(1,12,15)   //[0,1,2,3,4,5,6,7,8,9]
    // 索引反向忽略
    ints.copyWithin(2,4,2)     //[0,1,2,3,4,5,6,7,8,9]
    // 索引部分可用
    ints.copyWithin(4,7,10) // [0,1,2,3,7,8,9,7,8,9]
    
  • fill()

    • 填充数组,向一个已有数组插入全部或部分相同值
    • 第一个参数,要填充的内容
    • 第二个参数,开始索引(可选)
    • 第三个参数,结束索引(可选)
    • 负索引从末尾算
    • 修改原数组
    const zeroes = [0,0,0,0,0]
    
    // 每次默认认为重置回 [0,0,0,0,0]
    zeroes.fill(5)        // [5,5,5,5,5]
    zeroes.fill(6,3)      // [0,0,0,6,6]
    zeroes.fill(7,1,3)    // [0,7,7,0,0]
    zeroes.fill(8,-4,-1)  // [0,8,8,8,0]
    
    // 忽略超出数组边界,零长度及方向相反的索引范围
    // 索引过底忽略
    zeroes.fill(1,-10,-6) // [0,0,0,0,0]
    // 索引过高忽略
    zeroes.fill(1,10,15) // [0,0,0,0,0]
    // 索引反向忽略
    zeroes.fill(2,4,2) // [0,0,0,0,0]
    // 索引部分可用
    zeroes.fill(4,3,10) // [0,0,0,4,4]
    

7、转换方法

  • valueOf() 返回数组本身
  • toString() 返回每个值用逗号分隔的字符串
  • join()
    • 默认逗号
    • 传入参数为分隔符
  • null、undefined会作为空字符串表示

8、栈方法

  • 栈,后进先出,最近添加的项先被删除push()+pop()
  • push() 推入,插入
    • 接收任意数量参数,添加到数组末尾
    • 返回最新长度
    • 改变原数组
  • pop() 弹出,删除
    • 删除最后一项,减少数组length
    • 返回被删除的项
    • 改变原数组

9、队列方法

  • 队列 先进先出 push() + shift()
  • shift()
    • 删除数组第一项并返回它
    • 数组长度减一
  • unshift()
    • 与shift() 相反
    • 返回数组长度

10、排序方法

  • reverse()

    • 将数组反向排列
  • sort()

    • 默认按照升序重新排列(转为字符串比较)

    • 接收比较函数,判断哪个值在前面

      • 如果第一个在第二个前面则返回负值
      • 相等返回0
      • 如果第一个在第二个后面则返回正值
      // 返回负数,小的在前,升序
      function compare(value1, value2) {
        if(value1 < value2) {
          return -1
        }else if (value1 > value2) {
          return 1
        }
        return 0
      }
      
      // (a,b)=> a<b ? -1 : a>b ? 1 : 0
      
      // 如果都是数值
      function compare(value1, value2) {
        return value1 - value2
      }
      

11、操作方法

  • concat()

    • 在现有数组全部元素基础上创建新数组
    • 传入数组会把数组每一项添加到结果数组
    • 传入不是数组,则直接添加到数组末尾
    • 原数组不变
    • 打平方式可以重写
    let colors = ["red", "green", "blue"]
    let newColors = ["black", "brown"]
    let moreNewColors = {
      [Symbol.isConcatSpreadable]: true,
      length: 2,
      0: "pink",
      1: "cyan"
    }
    
    newColors[Symbol.isConcatSpreadable] = false
    
    // 强制不打平
    // ["red","green","blue","yellow",["black", "brown"]]
    colors.concat("yellow",newColors)
    
    // 强制打平类数组对象
    // ["red","green","blue","pink","cyan"]
    colors.concat(moreNewColors)
    
  • slice()

    • 创建一个包含原数组中一个或多个元素的新数组
    • 不改变原数组
    • 参数一,开始索引
    • 参数二,结束索引(不含)
    • 负值加上数组长度
  • splice()

    • 主要是在数组中间插入元素
    • 改变原数组
    • 返回删除的数组
    • 删除
      • 传两个参数
      • 第一个参数,要删除的第一个元素位置
      • 第二个参数,删除元素数量
    • 插入
      • 传三个及以上参数
      • 第一个参数开始位置
      • 第二个参数 0 (不删除)
      • 第三个及以上参数,要插入的元素
    • 替换
      • 传三个及以上参数
      • 第一个参数开始位置
      • 第二个参数删除数量
      • 第三个及以上参数,要插入的元素

12、搜索和位置方法

1、严格相等

  • 都接收两个参数,要查找的元素,起始搜索位置

  • 用 === 全等比较

  • indexOf() 返回在数组中的位置,没找到返回-1

  • lastIndexOf() 返回在数组中的位置,没找到返回-1

  • includes() 返回布尔值,表示是否至少找到一个匹配项

2、断言函数

  • 返回匹配信息
  • 函数接收三个参数,元素,索引,数组本身
  • 断言函数返回真值,表示匹配
  • find()
    • 从最小索引开始
    • 返回第一个匹配的元素
    • 第二个参数 表示断言函数this值
    • 找到匹配后不再继续检查
  • findIndex()
    • 最小索引开始
    • 返回第一个匹配的索引
    • 第二个参数 表示断言函数this值
    • 找到匹配后不再继续检查

13、迭代方法

  • 两个参数,

    • 以每一项为参数运行的函数
    • 可选的作为函数运行上下问的作用域对象(this)
  • 传给方法的函数接收三个参数,元素,索引,数组本身

  • 不改变调用它们的数组

  • every() 如果每一项函数都返回true,则返回true

  • filter() 函数返回true的项会组成数组后返回

  • forEach() 每一项都运行,没有返回值

  • map() 返回由每次函数调用的结果构成的数组

  • some() 如果有一项函数返回true,则返回true

14、归并方法

  • 迭代数组所有项,构建一个最终返回值
  • 都接收两个参数
    • 每一项都运行的归并函数
    • 可选的初始值
  • 归并函数接收四个参数,上一个归并值,当前项,当前项索引,整个数组
  • 函数返回值会作为下一次调用的第一个参数
  • 如果没有第二个参数,则第一次迭代从数组第二项开始,第一个参数是数组第一项,第二个参数是数组第二项。
  • reduce()
    • 从第一项到最后一项
  • reduceRight()
    • 从最后一项到第一项

3、定型数组

1、历史

  • 目的充分利用3D图形API和GPU加速,以便在元素上渲染复杂图形

1、WebGL

2、定型数组

  • CanvasFloatArray -> Float32Array

2、ArrayBuffer

3、DataView

4、定型数组

4、Map

  • Map 一种新的集合类型,真正的键/值存储机制
  • 大多数特性可以通过Object类型实现

1、基本API

  • 创建空映射 const m = new Map()

  • 创建同时初始化实例,可以传入一个可迭代对象。可迭代对象中的每个键/值对都会按照迭代顺序插入到新映射的实例中。

    // 使用嵌套数组初始化映射
    const m1 = new Map([
      ["key1": "value1"],
      ["key2": "value2"],
      ["key3": "value3"]
    ])
    m1.size // 3
    
    // 使用自定义迭代器
    const m2 = new Map({
      [Symbol.iterator]: function*() {
        yield ["key1": "value1"];
        yield ["key2": "value2"];
        yield ["key3": "value3"];
      }
    })
    m2.size // 3
    
    const m3 = new Map([[]])
    m3.has(undefined) // true
    m3.get(undefined) // undefined
    
  • Map可以用任何数据类型作为键,使用SameValueZero比较操作,基本相当于使用严格对象的标准来检查键的匹配性

  • set() 增加键/值对

    • 返回映射实例,可以连缀
    • object只能用数值、字符串或Symbol作为键不同
  • get() 获取键/值对

  • has() 查询键/值对

  • size 获取键/值对数量

  • delete() 删除键/值对

  • clear() 清空键/值对

  • 在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时仍保持不变

    const m = new Map()
    
    const objKey = {}
    const	objVal = {}
    const arrKey = []
    const arrVal = []
    
    m.set(objKey, objVal)
    m.set(arrKey, arrVal)
    
    objKey.foo = "foo"
    objVal.bar = "bar"
    arrKey.push("foo")
    arrVal.push("bar")
    m.get(objKey) // {bar: "bar"}
    m.get(arrKey) // ["bar"]
    

2、顺序与迭代

  • Map会维护键值对的插入顺序,可以根据插入顺序执行迭代操作

  • 映射实例可以提供一个迭代器,能以插入顺序生成[key, value]数组,可以通过entries() 方法(或Symbol.iterator属性)取得迭代器

    const m = new Map([
      ["key1": "val1"],
      ["key2": "val2"],
      ["key3": "val3"]
    ])
    m.entries === m[Symbol.iterator]
    
    for(let pair of m.entries()) {
      ...
    }
    for(let pair of m[Symbol.iterator]()){
     	...
    }
    // ["key1": "val1"],
    // ["key2": "val2"],
    // ["key3": "val3"]
    
  • 因entries()是默认迭代器,则可以直接对映射实例使用扩展操作 [...m]

  • 若不使用迭代器,而用回调方式,可以用映射的forEach(callback,opt_thisArg)方法并传入回调,依次迭代每个键值对。传入的回调接受第二个参数重写this

    const m = new Map([
      ["key1": "val1"],
      ["key2": "val2"],
      ["key3": "val3"]
    ])
    m.forEach((val,key)=>alert(`${key} -> ${val}`))
    // key1 -> val1
    ...
    
  • keys() 和values() 分别返回以插入顺序生成的键和值的迭代器

    const m = new Map([
      ["key1": "val1"],
      ["key2": "val2"],
      ["key3": "val3"]
    ])
    for(let key of m.keys()){
      ...
    }
    for(let val of m.values()){
      ...
    }
    
  • 键和值在迭代器遍历时可以修改,但映射内部的引用则无法修改。可以修改作为键或值对象的内部属性。

    const m1 = new Map([
      ["key1", "val1"]
    ])
    
    // 作为键的字符串原始值无法修改
    for(let key of m1.keys()) {
      key = "newKey"
      m1.get("key1") // val1
    }
    
    const keyObj = {id: 1}
    
    const m = new Map([
      [keyObj, "val1"]
    ])
    
    // 修改了作为键的对象的属性,但对象在映射内部仍然引用相同值
    for(let key of m.keys()) {
      key.id = "newKey"
      key // {id: "newKey"}
    }
    ketObj // {id: "newKey"}
    
    // 值一样。
    

3、选择object还是Map

1、内存占用

​ 给定固定大小的内存,Map大约比Object多存储50% 键值对

2、插入性能

Map稍微快一点,大量插入操作Map性能更好

3、查找速度

  • 大型Object和Map中查找键值对性能差异极小
  • 只包含少量键值对,Object速度可能更快
  • 把Object当数组的时候(连续整数作为属性)浏览器引擎会优化。
  • 涉及大量查找操作时,选择Object更好

4、删除性能

  • Map的delete() 操作都比插入和查找更快
  • 涉及大量删除操作时,选择Map

5、WeakMap

  • “弱映射(WeakMap)”,弱是指JavaScript垃圾回收程序对待“弱映射”中键的方式

1、基本API

  • 初始化 const wm = new WeakMap()

  • 弱映射的键只能是Object或继承自Object的类型

  • 值的类型没有限制

  • 初始化时填充弱映射

    const key1 = {id: 1}
    const key2 = {id: 2}
    const key3 = {id: 3}
    
    const wm1 = new WeakMap([
      [key1, "val1"],
      [key2, "val2"],
      [key3, "val3"]
    ])
    vm1.get(key1) // "val1"
    vm1.get(key2) // "val2"
    vm1.get(key3) // "val3"
    
    //只要有一个键无效 就会整个失败
    const vm2 = new WeakMap([
      [key1, "val1"],
      ["BAD", "val2"],
      [key3, "val3"]
    ])
    
    // 原始值可以先包装成对象再作为键
    const stringKey = new String("key1")
    const vm3 = new WeakMap([
      stringKey: "val1"
    ])
    
  • set() 添加

  • get() 获取

  • has() 查询

  • delete() 删除

  • 没有clear()

2、弱键

  • “weak” 表示弱映射的键是“弱弱地拿着”,不属于正式引用,不会阻止垃圾回收。但弱映射中值的不是“弱弱地拿着”。只要键存在,键值对就会存在于映射中,并被当作对值的引用。

    const wm = new WeakMap()
    wm.set({}, "val")
    
    • set() 方法初始化了一个新对象并将它用作字符串的键
    • 因为没有指向这个对象的其他引用,所以执行完后,这个对象键就会被当作垃圾回收。
    • 这个键值对就从弱映射中消失了,成为了一个空映射。
    • 因为值也没有被引用,所以这对键值对被破坏后,值本身也会成为垃圾回收的目标。
    const wm = new WeakMap()
    const container = {
      key: {}
    }
    wm.set(container.key, "val")
    function removeReference() {
      container.key = null
    }
    
    • container对象委会这一个弱映射键的引用,所以不会被垃圾回收。
    • 但如果调用removeReference摧毁最后一个引用,垃圾回收机制就会吧这个键值对清除。

3、不可迭代键

  • 因为WeakMap中的键值对任何时候都可能被摧毁
  • 不提供迭代能力
  • 不用clear() 方法
  • 限制用对象作为键,目的是为了保证只有通过键对象的引用才能取得值。如果使用原始值,没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串

4、使用弱映射

1、私有变量

  • 真正私有变量的一种新方式。私有变量会存储在弱映射中。以对象为键,以私有成员的字典为值。

    const wm = new WeakMap()
    
    class User {
     constructor(id) {
        this.idProperty = Symbol('id')
        this.setId(id)
     }
      setPrivate(property, value) {
        const privateMembers = wm.get(this) || {}
        privateMembers[property] = value
        wm.set(this, privateMembers)
      }
      getPrivate(property) {
        return wm.get(this)[property]
      }
      setId(id) {
        this.setPrivate(this.idProperty, id)
      }
      getId() {
        return this.getPrivate(this.idProperty)
      }
    }
    
    const user = new User(123)
    user.getId() // 123
    user.setId(456) 
    user.getId() // 456
    
    // 但是 并非真正私有
    wm.get(user)[user.idProperty] // 456
    
    • 通过闭包将WeakMap包装起来,可以和外界完全隔离
    const User = (()=> {
     const wm = new WeakMap()
      
      class User {
        constructor(id) {
          this.idProperty = Symbol('id')
          this.setId(id)
        }
        setPrivate(property, value) {
          const propertyMembers = wm.get(this) || {}
          propertyMembers[property] = value
          wm.set(this, propertyMembers)
        }
        getPrivate(property) {
          return wm.get(this)[property]
        }
        setId(id) {
          this.setPrivate(this.idProperty, id)
        }
        getId() {
          return this.getPrivate(this.idProperty)
        }
      }
      return User
    })()
    
    const user = new User(123)
    user.getId() // 123
    user.setId(456) 
    user.getId() // 456
    
    • 这样就防止了前面提到的访问,也完全陷入了闭包的模式

2、DOM节点元数据

  • WeakMap实例不会妨碍垃圾回收,适合保存关联元数据

    const wm = new WeakMap()
    const loginBtn = document.querySelector("#login")
    wm.set(loginBtn, {disabled: true})
    
    • 当节点从DOM树中删除后,垃圾回收程序就能立即释放其内存(其他地方没有引用)

6、Set

  • 集合类型,更像加强的Map。

1、基本API

  • 创建 const m = new Set()

  • 创建的同时初始化,可以传入可迭代对象

    // 使用数组初始化集合
    const s1 = new Set(["val1", "val2", "val3"])
    s1.size // 3
    
    // 使用自定义迭代器初始化集合
    const s2 = new Set({
      [Symbol.iterator]: function*() {
        yield "val1";
        yield "val2";
        yield "val3";
      }
    })
    s2.size // 3
    
  • add() 增加值 可连缀

  • has() 查询值

  • size 获取元素数量

  • delete() 删除某个

    • 返回布尔值,表示集合中是否包含要删除的值
  • clear() 删除所有

  • Set可以包含任何数据类型作为值,集合也使用SameValueZero操作。

    const s = new Set()
    const functionVal = function() {}
    const symbolVal = Symbol()
    const objectVal = new Object()
    
    s.add(functionVal)
     .add(symbolVal)
     .add(objectVal)
    
    s.has(functionVal) // true
    s.has(symbolVal) // true
    s.has(objectVal) // true 
    
    //SameValueZero 检查意味着独立的实例不会冲突
    s.has(function() {}) // false
    
  • 用作值的对象在自己的内容或属性修改时也不会改变

    const s = new Set()
    const objVal = {}
    const arrVal = []
    
    s.add(objVal).add(arrVal)
    
    objVal.bar = "bar"
    arrVal.push("bar")
    
    s.has(objVal) // true
    s.has(arrVal) // true
    

2、顺序与迭代

  • Set会维护插入值插入时的顺序,支持顺序迭代

  • 集合实例提供一个迭代器,能以插入顺序生成集合内容。可以通过values()方法及keys()或Symbol.iterator属性 取得这个迭代器

    const s = new Set(["val1","val2","val3"])
    s.keys === s.va
    lues
    s.values === s[Symbol.iterator]
    for(let value of s.values()) {
      ...
    }
    for(let value of s[Symbol.iterator]()){
      ...
    }
    
  • values() 是默认迭代器,可以直接使用扩展操作。

    const s = new Set(["val1","val2","val3"])
    [...s] // ["val1","val2","val3"]
    
  • entries() 方法返回一个迭代器

    const s = new Set(["val1","val2","val3"])
    
    for(let pair of s.entries()) {
    	pair 
      // ["val1","val1"]
      // ["val2","val2"]
      // ["val3","val3"]
    }
    
  • forEach() 传入回调迭代每个键值对

    const s = new Set(["val1","val2","val3"])
    
    s.forEach((val, dupVal)=> alert(`${val}->${dupVal}`))
    
  • 修改集合中值的属性不影响身份

    const s1 = new Set(["val1"])
    
    // 字符串原始值作为值无法修改
    for(let value of s1.values()) {
      value = "newVal"
      s1.has("val1") // true
    }
    
    const valObj = {id: 1}
    const s2 = new Set([valObj])
    
    // 修改值对象的属性,但对象仍在集合中
    for(let value of s2.values()) {
      value.id = "newVal"
      s2.has(valObj) // true
    }
    valObj // {id: "newVal"}
    

3、正式定义集合操作

  • 子类化Set,定义一个使用函数库
  • 某些Set操作是有关联性的,最好能支持处理任意多个集合实例
  • Set保留插入顺序,所有方法返回的集合必须按序
  • 尽可能高效使用内存,避免集合数组间相互转换能节省对象初始化成本
  • 不要修改已有的集合实例(union(a,b)a.union(b)应该返回包含结果的新集合实例)
class XSet extends Set {
	union(...sets) {
    return XSet.union(this, ...sets)
  }
  
  intersection(...sets) {
    return XSet.intersection(this, ...sets)
  }
  
  difference(set) {
    return XSet.difference(this, set)
  }
  
  symmetricDifference(set) {
    return XSet.symmetricDifference(this, set)
  }
  
  cartesianProduct(set) {
    return XSet.cartesianProduct(this, set)
  }
  
  powerSet() {
    return XSet.powerSet(this)
  }
  
  // 返回两个或更多集合的并集
  static union(a, ...bSets) {
    const unionSet = new XSet(a)
    for(const b of bSets) {
      for(const bValue of b) {
        unionSet.add(bValue)
      }
    }
    return unionSet
  }
  
  // 返回两个会更多集合的交集
  static intersection(a, ...bSets) {
    const intersectionSet = new XSet(a)
    for(const aValue of intersectionSet) {
      for(const b of bSets) {
        if(!b.has(aValue)) {
          intersectionSet.delete(aValue)
        }
      }
    }
    return intersectionSet
  }
  
  // 返回两个集合的差集
  static difference(a, b) {
    const differenceSet = new XSet(a)
		for(const bValue of b) {
      if(a.has(bValue)) {
        differenceSet.delete(bValue)
      }
    }
    return differenceSet
  }
  
  // 返回两个集合的对称差集
  static symmetricDifference(a, b) {
    return a.union(b).difference(a.intersection(b))
  }
  
  // 返回两个集合(数组对形势)对笛卡尔积
  // 必须返沪数组集合,因为笛卡尔积可能包含相同值的对
  static cartesianProduct(a, b) {
    const cartesianProductSet = new XSet()
    for(const aValue of a) {
      for(const bValue of b) {
        cartesianProductSet.add([aValue, bValue])
      }
    }
    return cartesianProductSet
  }
  
  // 返回一个集合的幂集
  static powerSet(a) {
    const powerSet = new XSet().add(new XSet())
    for(const aValue of a) {
      for(const set of new XSet(powerSet)) {
        powerSet.add(new XSet(set).add(aValue))
      }
    }
    return powerSet
  }
}

7、WeakSet

  • 弱集合,”weak“ 是指垃圾回收程序对待“弱集合”中值的方式

1、基本API

  • 创建const ws = new WeakSet()

  • 弱集合的值只能是Object或者继承自Object的类型。

  • 初始化时填充弱集合,接收一个可迭代对象。

    const val1 = {id: 1}
    const val2 = {id: 2}
    const val3 = {id: 3}
    const ws1 = new WeakSet([val1, val2, val3])
    
  • 初始化有一个失败则全失败

  • 原始值可以先包装成对象在用作值

  • add() 添加新值 可以连缀

  • has() 查询

  • delete() 删除

2、弱值

  • WeakSet 中 “weak” 表示弱集合的值是“弱弱的拿着”,不会阻止垃圾回收
  • 与WeakMap相同

3、不可迭代值

  • 任何时候都可能被摧毁,没有迭代能力
  • 没有clear() 方法
  • 限制用对象作为值,目的是为了保证只有通过对象的引用才能取得值。如果使用原始值,没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了

4、使用弱集合

  • 给对象打标签

    Set打标签,如果dom移除了但还是在集合中,垃圾回收机制不能回收它。

    // Set
    const disabledElements = new Set()
    const loginButton = document.querySector("#login")
    // 打上禁用标签
    disabledElements.add(loginButton)
    

    WeakSet,垃圾回收程序就能回收它。

8、迭代与扩展操作

  • 迭代器和扩展操作符对集合引用类型特别有用,让集合之间相互操作、复制和修改异常方便。

    • Array
    • 定型数组
    • Map
    • Set
  • 都支持顺序迭代,可以传入for-of循环

    let iterableThings = [
      Array.of(1,2),
      typedArr = Int16Array.of(3,4),
      new Map([[5,6],[7,8]]),
      new Set([9,10])
    ]
    for(const iterableThing of iterableThings) {
      for(const x of iterableThing) {
        console.log(x)
      }
    }
    
  • 扩展操作符在可迭代对象浅复制的时候有用

    let arr1 = [1,2,3]
    let arr2 = [...arr1]
    arr1 === arr2 // false
    
  • 期待可迭代对象的构造函数,传入可迭代对象就可以复制

    let map1 = new Map([[1,2],[3,4]])
    let map2 = new Map(map1)
    
  • 构建数组部分参数

    let arr1 = [1,2,3]
    let arr2 = [0, ...arr1, 4, 5]// [0,1,2,3,4,5]
    
  • 浅复制只会复制对象引用

    let arr1 = [{}]
    let arr2 = [...arr1]
    arr1[0].foo = "bar"
    arr2[0] // {foo: "bar"}
    
  • 配合Array.of() Array.from()

    let arr1 = [1,2,3]
    
    // 数组复制到定型数组
    let typedArr1 = Int16Array.of(...arr1)
    let typedArr2 = Int16Array.from(arr1)
    
    // 数组复制到映射
    let map = new Map(arr1.map((x)=>[x, 'val' + x]))
    
    // 数组复制到集合
    let set = new Set(typedArr2)
    
    //集合复制会数组
    let arr2 = [...set]