《JavaScript高级程序设计》第六章:集合引用类型

77 阅读30分钟

前言

本文按照JS红宝书第六章集合引用类型的顺序整理。看完这篇文章,你将能回答如下的问题:

问:

  1. 创建数组的方式?【6.2 1】
  2. new Array(),只传数字和只传字符串有区别吗?【6.2 1】
  3. 不使用 new 关键字能否执行【6.2 1】
  4. Array.from()使用场景是什么?【6.2 1】
  5. Array.from()的几个参数分别是干什么的?【6.2 1】
  6. Array.of 的使用场景是什么?最早有哪个方法是跟他作用类似的?【6.2 1】
  7. instanceof和Array.isArray的区别【6.2 4】
  8. 说说数组的迭代器方法?【6.2 5】
  9. 数组一共有哪些方法,能分类说说吗?数组有哪些排序方法,会改变数组本身吗?【6.2 10】
  10. 数组的fill和copyWithin方法区别?【6.2 6】
  11. 数组执行toString valueOf toLocaleString方法,返回值有什么不同?【6.2 7】
  12. join方法默认是用什么进行分割?【6.2 7】
  13. 数组的splice方法有哪些用途?【6.2 11】
  14. 了解数组的定型数组吗?了解ArrayBuffer吗?了解Int32Array构造函数吗?【6.3】
  15. map有哪些属性和方法?set有哪些属性和方法?【6.4 6.6】
  16. WeakMap和Map的区别?WeakSet和Set的区别?【6.5 6.7】

6.2 数组:

注意,Object相关知识直接看第八章,我的另一篇文章

1. 创建数组

初始化 length 为 20 的数组

// 创建数组,构造函数,传入数字
let arr1 = new Array(20)
console.log(arr1, 'arr1');

传入三个字符串:

  • 传一个数字,设置的是数组的长度
  • 传一个字符串,设置的是数组的值
let arr2 = new Array('red', 'blue', 'green')
console.log(arr2, 'arr2'); // ['red', 'blue', 'green']

可以不使用new 效果一致

let arr3 = Array(3)
let arr4 = Array("Gred")
console.log(arr3, 'arr3');
console.log(arr4, 'arr4');

字面量创建数组:

  • 最后一个元素可以加逗号,实际上还是 2 个元素
let arr5 = [1,2,3]
let arr6 = []
let arr7 = [1,2,]
console.log(arr5, 'arr5');
console.log(arr6, 'arr6');
console.log(arr7, 'arr7');

注意:使用字面量形式,后台不会调用 Array 构造函数

Array.from处理字符串

console.log(Array.from('Matt'), 'Array.from("Matt")'); // ['M', 'a', 't', 't'] 

Array.from 处理 map 和 set 数据结构

const m = new Map().set(1, 2)
                   .set(3, 4);
const s = new Set().add(1)
                   .add(2)
                   .add(3)
                   .add(4);
console.log(m, 'm');
console.log(s, 's');
console.log(Array.from(m), 'Array.from(m)');
console.log(Array.from(s), 'Array.from(s)');

 Array.from用于任何可以迭代的对象

const iter = {
    *[Symbol.iterator] () {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    }
}
console.log(iter, 'iter');
console.log(Array.from(iter), 'Array.from(iter)');

Array.from()转换arguments

// Array.from()转换arguments
function getArrayArgs() {
    return Array.from(arguments)
}
console.log(getArrayArgs(1,2,3,4), 'getArrayArgs(1,2,3,4)');

转换伪数组

// 转换伪数组,
let arrayLikeObject = {
    0:1,
    1:2,
    2:3,
    length: 3
}
console.log(Array.from(arrayLikeObject), 'Array.from(arrayLikeObject)');

第二个参数,对数组的元素进行操作,如下是对每个元素*2,

const a1 = [1,2,3,4]
console.log(Array.from(a1, x => x * 2), 'Array.from(a1, x => x * 2)');

Array.from 第三个参数,改 this 指向,注意箭头函数无用,所以如下第二个输出是 NaN,全局的 this 找不到 y 值

// Array.from()的第三个操作
console.log(Array.from(a1, function (x) { return x * this.y},  {y: 3}), '第三个参数,');
console.log(Array.from(a1, (x) => { return x * this.y},  {y: 3}), '第三个参数,');

Array.of()方法,把一组参数转化为数组,替换 Array.prototype.slice.call(arguments),注意后者必须在函数里面执行

// Array.prototype.slice.call()
function setArray() {
    return Array.prototype.slice.call(arguments)
}
console.log(setArray(1,2,3,4), '函数里执行Array.prototype.slice.call(arguments)');
console.log(Array.prototype.slice.call({
    0: 1,
    1: 2,
    2: 3,
    3: 4,
    length: 4
}), '函数里执行Array.prototype.slice.call(arguments)');
console.log(Array.of(1,2,3,4), 'Array.of(1,2,3,4)');

2. 数组空位

创建数组,只有逗号,没有值,默认值就是 undefined

let options = [,,,,,]
console.log(options, 'options');

// 遍历他
for (const item of options) {
   console.log(item === undefined, 'item');
}

使用 Array.from 和 Array.of 再执行

// Array.from创建
console.log(Array.from([,,,]), 'Array.from([,,,])');

// Array.of创建
console.log(Array.of(...[,,,]), 'Array.of(...[,,,])');

for (const [index, value] of options.entries()) {
    console.log(value, 'value');
}

ES6 之前的方法,忽略空字符串,map 和 join 如下

const options2 = [1,,,5]
console.log(options2.map(() => 6), 'map忽略undefined');
console.log(options2.join('-'), 'join忽略空字符');

3. 数组索引

通过 length 访问长度;通过中括号跟数字访问具体数值,访问不存在的索引返回 undefined

let arr = ["red", "blue", 'green']
console.log(arr, 'arr');
console.log(arr.length, 'arr.length');
console.log(arr[0], 'arr[0]');
console.log(arr[3], 'arr[3]');

数组的 length 属性可以修改,注意,如果 length 改成很大的值,中间的空位会变成 undefined。尽量不要出现空位

// length属性可以改
let arr1 = [1,2,3,4]
arr1.length = 3
console.log(arr1, 'arr1改后的值');
arr1.length = 10
console.log(arr1, 'arr1扩容后的值');

通过 length 给数组最后一个元素增加值

let arr2 = [1,2]
arr2[arr2.length] = 3
console.log(arr2, 'arr2增加后的情况'); // [1,2,3]

4. 检测数组 instanceof Array.isArray

检测数组的方式

// intanceof
let arr = [1,2,3]
console.log(arr instanceof Array, 'arr instanceof Array'); // true
console.log(Array.isArray(arr), 'Array.isArray(arr)'); // true

这两个检测数组方式区别:

  1. 机制不同:instanceof 是用于判断这个实例在某个构造函数的原型链上,Array.isArray 只能检测数组
  2. 跨上下文:Array.isArray 任何时候判断数组都没问题。而instanceof,主窗口创建 arr 实例传递给 iframe 里面去,在 iframe 里面判断 arr instanceof Array 是错误的,因为只有 iframe 里面的 arr 实例才再 iframe 里面的 Array 的原型链上,每个窗口都有自己的执行环境
// 主窗口创建arr
let arr = [1,2,3]
// arr传递给iframe,在iframe里面执行
console.log(arr instanceof Array) // 是false

5. 迭代器方法 keys values entries

ES6 数组暴露 3 个用于检测数组内容的方法,keys() values() entries()。他们都返回迭代器,可以直接和 Array.from 转换为数组

let arr = ['foo', 'bar', 'baz', 'qux']
const aKeys = Array.from(arr.keys())
const aValues = Array.from(arr.values())
const aEntries= Array.from(arr.entries())
console.log(aKeys, 'aKeys');
console.log(aValues, 'aValues');
console.log(aEntries, 'aEntries');

和解构赋值结合起来

for (const [idx, valuex] of arr.entries()) {
    console.log(idx, valuex, 'idx valuex');
}

6. 复制 fill 和填充方法 copyWithin

fill(要填充值,开始索引,结束索引)

// 数组方法
let arr = [0, 0, 0, 0, 0]
// 均填充为1
arr.fill(1)
console.log(arr, 'arr');
// 复原
arr.fill(0)
console.log(arr, 'arr');

填充索引大于等于3

arr.fill(1, 3)
console.log(arr, 'arr');
// 复原
arr.fill(0)

大于等于1且小于3

arr.fill(7, 1, 3)
console.log(arr, 'arr');

负索引大于等于1小于4

console.log(arr.fill(8, -4, -1), 'arr.fill(8, -4, -1)');

超出边界,忽略,最后一个是-1,然后往前累加

arr.fill(0)
console.log(arr.fill(1, -10, -6), 'arr.fill(1, -10, -6)');

边界部分可用

console.log(arr.fill(4, 3, 10), 'arr.fill(4, 3, 10)');

copyWithin(插入位置,开始索引,结束索引)

注意,该方法,先复制,再去插入,

复制索引 0 开始的内容,插入到索引 5,参数 1 代表要插入到的位置

let arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(arr1.copyWithin(5), 'arr1.copyWithin(5)');

复制从索引 5 开始的内容插入到索引 0

console.log(arr1.copyWithin(0, 5), 'arr1.copyWithin(0, 5)');

复制索引0到索引3,不包括 3,插入到索引4

console.log(arr1.copyWithin(4, 0, 3));

负数索引,复制从-7 开始,到-3 结束,不包括-3 索引的值,4 个数,从-4 开始插入,就是 4,5,6,7 插入到原本的 7 开始插入,4 个数

console.log(arr1.copyWithin(-4, -7, -3), 'arr1.copyWithin(1, -15, -12)');

索引过低,忽略

// 索引过低
console.log(arr1.copyWithin(1, -15, -12), 'arr1.copyWithin(1, -15, -12)');

部分可用

7. 转换方法 toString() valueOf() toLocaleString()

对数组调用 toString() valueOf() toLocaleString()

toString():对数组的每个值转化为字符串,再用逗号分隔

valueOf:返回数组本身

let arr = ["red", "blue", "green"]
console.log(arr.toString(), 'arr.toString()');
console.log(arr.valueOf(), 'arr.valueOf()');
console.log(arr, 'arr');

alert 打印数组,会再后台调用 toString()方法,alert 期待字符串

alert(arr)

toLocaleString()方法会调用数组的每个值的toLocaleString 方法,返回其对应内容再拼接位字符串

let person1 = {
    toLocaleString() {
        return "Nikolaos"
    },
    toString () {
        return 'Nicholas'
    }
}
let person2 = {
    toLocaleString() {
        return "Grigorios"
    },
    toString () {
        return 'Greg'
    }
}
let arr2 = [person1, person2]
console.log(arr2.toString(), 'arr2.toString()'); // "Nicholas,Greg"
console.log(arr2.toLocaleString(), 'arr2.toLocaleString()'); // "Nikolaos,Grigorios"
alert(arr2) // 同样这里alert还是会调用每个值的toString(),然后拼接位字符串

使用 join 字符串,改变分隔符,如果不给 join 传递任何字符,或者传 undefined,join(undefined),则默认用逗号取分隔的形式

let arr3 = ["red", "blue", "green"]
console.log(arr3.join("||"), 'arr3.join("||")');
console.log(arr3.join("-"), 'arr3.join("-")');
console.log(arr3.join(), 'arr3.join("")');

null 或者 undefined,被 toString 如何处理

let arr4 = [1, 2, undefined, null]
console.log(arr4.toString(), 'arr4.toString()'); // 1,2,,

8. 栈方法 push pop

栈是一种后进先出的数据结构

  • push()推入,可以接受任意数量的参数;执行返回修改后数组的长度
  • pop()弹出,弹出数组的最后一项;执行返回 pop 出来的指定元素
let arr = [1,2,3]
console.log(arr.push(5), 'arr.push(4)');
console.log(arr.pop(), 'arr.pop()');

9. 队列方法 shift unshift

队列是一种先进先出的数据结构

  • shift,删除开头的第一个元素,可以和 push 形成队列操作
let arr2 = [1,2,3,4]
console.log(arr2.push(5), 'arr2.push(5)'); // 5
console.log(arr2.shift(), 'arr2.shift()'); // 1

  • unshift,从开头插入一个元素,可以和 pop 形成反向的队列操作
let arr3 = [1,2,3,4]
console.log(arr3.unshift(0), 'arr3.unshift(5)'); // 5
console.log(arr3.pop(), 'arr3.pop()'); // 4

10. 排序方法 reverse sort

reverse 反转方法,会将数组整个顺序反转,改变原数组

// reverse()反转方法
let arr = [1, 2, 3, 4, 5]
console.log(arr.reverse(), 'arr.reverse');
console.log(arr, 'arr');

sort 排序方法,默认升序,因为 sort 方法会把每一个值调用 String(),比较字符串来决定顺序,所以如下结果中 10 和 15 在 5 的后面

// sort()方法默认升序
let arr2 = [0, 1, 5, 10, 15]
console.log(arr2.sort(), 'arr2.sort()');

sort 方法接受一个函数:

  • 如果 value1 < value2,value1 在 value2 的签名,那么 value1 - value2 肯定是负数,所以返回-1
  • 如果 value1 > value2,那么 return 1,;表示 value1 应该在 value2 的后面,以上两点是正序
// sort()接受一个函数
function compare (value1, value2) {
    if (value1 < value2) {
        return -1
    } else if (value1 > value2) {
        return 1
    } else {
        return 0
    }
}
let arr3 = [0, 1, 5, 10, 15]
console.log(arr3.sort(compare), 'arr3.sort(compare)');

倒序

// sort()接受一个函数
function compare (value1, value2) {
    if (value1 > value2) {
        return -1
    } else if (value1 < value2) {
        return 1
    } else {
        return 0
    }
}
let arr3 = [0, 1, 5, 10, 15]
console.log(arr3.sort(compare), 'arr3.sort(compare)');

纯数值,或者 Date 对象, 升序:

  • value1 - value2 是负,value1 < value2, 升序
// sort()接受一个函数
function compare2 (value1, value2) {
    return value1 - value2
}
let arr4 = [0, 1, 5, 10, 15]
console.log(arr4.sort(compare2), 'arr4.sort(compare)');

倒序

// sort()接受一个函数
function compare2 (value1, value2) {
    return value2 - value1
}

11. 操作方法 concat、slice、splice

concat 方法,不改变原数组 默认 slice 能够打平传入的数组,传入数值直接合并,Symbol.isConcatSpreadable 能够关闭一个数组的合并功能

// concat()合并方法
let arr = [1, 2, 3, 4, 5]
// concat能够打平传入的数组
console.log(arr.concat(6,[7,8]), 'arr.concat(5,6,[7,8])');
// 关闭打平
let newArr = [7, 8]
newArr[Symbol.isConcatSpreadable] = false
console.log(arr.concat(6,newArr), 'arr.concat(9,10,[11])');

slice()方法 不改变原数组截取开始索引到结束索引的值,不包括结束索引

// slice
let colors = ["red", "green", "blue", "yellow", "purple"]
console.log(colors.slice(1), 'colors.slice(1)');
console.log(colors.slice(0, 3), 'colors.slice(0, 3)');

splice 方法 改变原数组

  • splice(0,1)删除第 0 个元素
  • splice(1, 0, 33, 44) 从索引 1 位置,插入 33 和 44,不删除旧的
  • splice(2,1,33,44) 从索引 2 的位置,删除他,并且在这里插入 33 和 44,2 被替换为 33
let colors2 = [1, 2, 3, 4, 5, 6]
colors2.splice(0, 2)
// 删除:0,1索引元素
console.log(colors2, 'colors2.splice(0, 2)'); // [3, 4, 5, 6]
// 插入:从第1个位置开始插入33和44的元素
colors2.splice(1, 0, 33, 44)
console.log(colors2, 'colors2.splice(1, 0, 33, 44)'); // [3, 33, 44, 4, 5, 6]
// 替换:从第2个位置开始,删掉1个元素,并插入"red" "green"
colors2.splice(2, 1, "red", "green")
console.log(colors2, 'colors2.splice(2, 1, "red", "green")'); // [3, 33, 'red', 'green', 4, 5, 6]

12. 搜索和位置方法 indexOf lastIndexOf includes find findIndex

  • indexOf 和 lastIndexOf ,查找元素在数组中第一次出现的索引;
  • includes,返回要找的元素在数组中是否出现过
  • indexOf(3,4) 要找的元素是 3,从第四个元素开始往后找,包括第四个;includes(3,4)顺序类
  • lastIndexOf(3,4)要找的是 3,从第四个索引的位置往前找,
let numbers = [1,2,3,4,5,4,3,2,1]
console.log(numbers.indexOf(4), 'numbers.indexOf(4)'); // 3
console.log(numbers.lastIndexOf(4), 'numbers.lastIndexOf(4)'); // 5
console.log(numbers.includes(4), 'numbers.includes(4)'); // true

console.log(numbers.indexOf(3, 4), 'numbers.indexOf(3, 4)'); // 6
console.log(numbers.lastIndexOf(5, 4), 'numbers.lastIndexOf(5, 4)'); // 4
console.log(numbers.includes(5, 6), 'numbers.includes(5, 6)'); // false

是否包含对象

let person = {
    name: 'Nicholas'
}
let people = [
    {
        name: 'Nicholas'
    }
]
let morePeople = [person]
console.log(people.indexOf(person), 'people.indexOf(person)'); // false
console.log(morePeople.indexOf(person), 'morePeople.indexOf(person)'); // false
console.log(people.includes(person), 'people.includes(person)'); // false
console.log(morePeople.includes(person), 'morePeople.indexOf(people)'); // true

断言函数 find 和 findIndex,找到指定项后不会继续往后查找,注意也接受第二个参数,绑定 this

const people1 = [
    {
        name: 'Mike',
        age: 27
    },
    {
        name: 'John',
        age: 28
    }
]
let item = people1.find(item => item.age < 28)
let itemIndex = people1.findIndex(item => item.age < 28)
console.log(item, 'item');
console.log(itemIndex, 'itemIndex');

13. 迭代方法 every some filter map forEach

  • every,每一项都满足条件,才为 true
  • some,有一项满足条件,就是 true
  • filter,筛选出指定条件的元素
  • map,生成新数组
  • forEach,类似于 for 循环
// every和some
let arr = [1, 2, 4, 6, 8]
let flag1 = arr.some(item => item % 2 === 0)
let flag2 = arr.every(item => item % 2 === 0)
console.log(flag1, 'flag1');
console.log(flag2, 'flag2');

// filter
let newArr = arr.filter(item => item < 4)
console.log(newArr, 'newArr');

// map 返回新数组
let newArr2 = arr.map(item => item * 2)
console.log(newArr2, 'newArr2');

// forEach
arr.forEach((item, index, target) => {
  console.log(item, index, target, 'item, index, target');
})

14. 归并方法 reduce reduceRight

归并操作 reduce

  • reduce 参数 1 是一个回调函数,参数 2 是迭代的初始值,该方法会返回一个迭代的最终值
  • 回调函数,参数 1 是上一个迭代值,每次迭代 return 的 值将作为下一次的迭代值;参数 2 是当前值;参数 3 是当前值的索引值,参数 4 是数组
// reduce
let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce((prev, cur, index, array) => {
  return prev + cur
}, 0)
console.log(sum, 'sum');

归并操作 reduceRight,从反方向开始遍历

// reduceRight
let sum2 = arr.reduceRight((prev, cur) => {
  console.log(prev, 'prev'); // 0 5 9 12 14 15
  console.log(cur, 'cur'); // 5 4 3 2 1
  return prev + cur
}, 0)
console.log(sum, 'sum');

6.3 定型数组

历史

定型数组:ES 新增的结构,目的=>提升向原生库传输数据的效率

  1. WebGL

早期,JS 和 WebGL 之间运行传输数据时存在性能问题,JS 在内存中的双精度浮点值的格式和 WebGL 中的格式不匹配

  1. 定型数组

所以出现了定型数组,增加 Float32Array,可以直接传给底层图形驱动程序,也可以从底层直接拿到数据

ArrayBuffer

Float32Array 是一种“视图”,可以允许 JS 运行时访问一块名为 ArrayBuffer预分配内存

ArrayBuffer 是所有定型数组视图引用的基本单位

ArrayBuffer构造函数,在内存中分配特定数量的字节空间

let buff1 = new ArrayBuffer(16)
console.log(buff1, 'buff1');
console.log(buff1.byteLength, 'buff1.byteLength'); // 16

一旦创建,不能调整大小,但是可以通过 slice 方法拷贝新的实例

// 通过slice调整大小
let buff2 = new ArrayBuffer(16)
let buff3 = buff2.slice(4, 12)
console.log(buff3, 'buff1');
console.log(buff3.byteLength, 'buff3.byteLength'); // 8

没有 concat 方法

// buff2.concat()

注意点:

  1. 分配失败直接报错
  2. 分配最大内存不超过 2 ^ 53 - 1
  3. 声明 ArrayBuffer 会将所有二进制初始化为 0
  4. 分配的堆内存可以被当成垃圾回收,不用手动释放

DataView

DataView: 第一个允许读取 ArrayBuffer 的视图,转为文件 I/O 和网络 I/O 涉及,他的 API 支持对缓冲数据的高度控制,相比其他类型视图性能差一些

比如对已有的 ArrayBuffer 读取或者写入时才能创建 DataView 实例:

默认使用整个组件:

// DataView 默认使用整个ArrayBuffer
const fullDataView = new DataView(buf)
console.log(fullDataView, 'fullDataView');
console.log(fullDataView.byteOffset, 'fullDataView.byteOffset'); // 0
console.log(fullDataView.byteLength, 'fullDataView.byteLength'); // 16
console.log(fullDataView.buffer === buf, 'fullDataView.buffer === buf'); // true

选择从 0-8 的字节长度

  // 构造函数构建一个可选的偏移量和字节长度
  const firstHalfDataView = new DataView(buf, 0, 8)
  console.log(firstHalfDataView.byteOffset, 'firstHalfDataView.byteOffset'); // 0
  console.log(firstHalfDataView.byteLength, 'firstHalfDataView.byteLength'); // 8
  console.log(firstHalfDataView.buffer === buf, 'firstHalfDataView.buffer === buf'); // true

使用剩余的缓冲数

// 如果不指定,会使用剩余的缓冲数
const secondHalfDataView = new DataView(buf, 8)
console.log(secondHalfDataView.byteOffset, 'secondHalfDataView.byteOffset'); // 8
console.log(secondHalfDataView.byteLength, 'secondHalfDataView.byteLength'); // 8
console.log(secondHalfDataView.buffer === buf, 'secondHalfDataView.buffer === buf'); // true

ElementType:

Int8: 8 位有符号整数,-128~127,[-2^8,]

Unit8: 8 位无符号整数,0~255

Int16:16 位有符号整数,-32768 ~ 32767

Unit16:16 位无符号整数,0~65353

Int32:32 位有符号整数,-2147483648~2147483647

Unit32:32 位 无符号整数,0~4294967295

Float32:32 位 IEEE-754 浮点数,-3.4e+38~3.4e+38

Float64:64 位 IEEE-754 浮点数,-1.7e+308~1.7e+308

API会对应不同的ElementType

// 分配2个字节
const buf2 = new ArrayBuffer(2)
const view = new DataView(buf2)

检查第一个数和第二个数

console.log(view.getInt8(0), 'view.getInt8(0)'); // 0
console.log(view.getInt8(1), 'view.getInt8(0)'); // 0
// 检查整个缓冲
console.log(view.getInt16(0), 'view.getInt16(0)'); // 0

将整个缓冲设置为1:

  • view.setUint8(0, 255),把第 0 个数值设置为 255,0XFF 是 255 的十六进制表达式
  • getInt8,8 位有符号,设置为 255,超过了 127,就显示为 01
  • getUint8(0),无符号,值范围是 0~255,没有超过,正常显示
// 将整个缓冲设置为1
// 将第0个字节的值设置为255,255的二进制表示是11111111(2 ^ 8 - 1)
view.setUint8(0, 255)
view.setUint8(1, 0XFF)
console.log(view, 'view');
console.log(view.getInt8(0), 'view.getInt8(0)'); // -1
console.log(view.getInt8(1), 'view.getInt8(1)'); // -1
console.log(view.getUint8(0), 'view.getUint8(0)'); // 255
console.log(view.getUint8(1), 'view.getUint8(1)'); // 255

字节序

字节序是计算系统维护的一种字节顺序。DataView,只支持两种约定:大端字节序和小端字节序。大端字节序是最高有效位保存在第一个字节,最低有效位保存在最后一个字节。小端字节序正好相反

DataView,默认大端字节序,如下,0X8001,默认 80 在前面是高字节序,01 在后面是低字节序

const buf3 = new ArrayBuffer(2)
const view3 = new DataView(buf3)
view.setUint8(0, 0x80) // 最左边位是1
view.setUint8(1, 0x01) // 最右边位是1
// 0x8001 = 2 ^ 15 + 2 ^ 1= 32769 = 8 * 16 ** 3 + 1 * 16 ** 1
console.log(view.getUint16(0), 'view.getUint16(0)'); // 32769

通过低字节序来获取,参数 2 设置为 true 就是开启低字节序,0x0180 = 1 * 16 ** 2 + 8 * 16 ** 1

console.log(view.getUint16(0, true), 'view.getUint16(0)'); // 32769

根据顺序写入

// 按大端字节序写入
view3.setUint16(0, 0x0004)
console.log(view3.getUint16(0), 'view3.getUint16(0'); // 4

// 按小端字节序写入
// 2 * 16 ** 2
console.log(view3.setUint16(0, 0x0002, true));
console.log(view3.getUint16(0), 'view3.getUint16(0)'); // 512
// 0x02
console.log(view3.getUint8(0), 'view3.getUint8(0)'); // 2
// 0x00
console.log(view3.getUint8(1), 'view3.getUint8(1)'); // 0

边界情况

dataview的值在写入缓冲里会尽最大努力把一个值转换为适当的类型,后背为0,如果无法转化则抛出错误

const buf5 = new ArrayBuffer(1)
const view5 = new DataView(buf5)

view5.setInt8(0, 1.5)
console.log(view5.getInt8(0)); // 1

view5.setInt8(0, [4])
console.log(view5.getInt8(0)); // 4

view5.setInt8(0, 'f')
console.log(view5.getInt8(0)); // 0

view5.setInt8(0, Symbol())
console.log(view5.getInt8(0));

定型数组

创建一个 12 字节的缓冲,创建一个引用该缓冲的 Int32Array

// 12字节的缓冲
const buf = new ArrayBuffer(12)
// 创建引用该缓冲的Int32Array,
const ints = new Int32Array(buf)
console.log(ints, 'ints');
console.log(ints.length, 'ints.length'); // 3

他知道自己每个元素需要四个字节,所以 length 是 3

创建一个长度为 6 的 Int32Array,每个数值使用 4 个字节(32 位),所以 byteLength 就是 24 字节

const ints2 = new Int32Array(6)
console.log(ints2.length, 'ints.length'); // 6
console.log(ints2.buffer.byteLength, 'ints2.buffer.byteLength'); // 24

创建一个包含 [2,4,6,8] 的 Int32Array,长度为 4,每个值占用 4 个字节,就是 16 个字节

const ints3 = new Int32Array([2,4,6,8])
console.log(ints3.length, 'ints.length'); // 4
console.log(ints3.buffer.byteLength, 'ints2.buffer.byteLength'); // 16

复制 ints3 的值,创建一个新的 Int16Array,每个值使用 2 个字节(16 位),byteLength 就是 4 * 2 是 8 个字节

const ints4 = new Int16Array(ints3)
console.log(ints4.length, 'ints.length'); // 4
console.log(ints4.buffer.byteLength, 'ints4.buffer.byteLength'); // 8

基于普通的数组来创建一个Int16Array

const ints5 = new Int16Array([2,4,6,8])
console.log(ints5.length, 'ints.length'); // 4
console.log(ints5.buffer.byteLength, 'ints5.buffer.byteLength'); // 8

基于给定的值创建一个Float32Array

const ints6 = Float32Array.of(3.14, 2.718, 1.618)
console.log(ints6.length, 'ints.length'); // 3
console.log(ints6.buffer.byteLength, 'ints6.buffer.byteLength'); // 12
console.log(ints6[2], 'ints6.buffer.byteLength'); // 1.6180000305175781

BYTES_PER_ELEMENT,返回每个值的大小,可以对构造函数直接使用

console.log(ints5.BYTES_PER_ELEMENT, 'ints5.BYTES_PER_ELEMENT'); // 2
console.log(ints6.BYTES_PER_ELEMENT, 'ints6.BYTES_PER_ELEMENT'); // 4

给 0 作为默认值

const ints7 = new Int32Array(4)
console.log(ints7[0], 'ints7[0]'); // 0
console.log(ints7[1], 'ints7[1]'); // 0
console.log(ints7[2], 'ints7[2]'); // 0
console.log(ints7[3], 'ints7[3]'); // 0

支持的方法

[]
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
indexOf()
join
keys
lastIndexOf
length
map
reduce
reduceRight
reverse
slice
some
sort
toLocaleString
toString
values

支持遍历

for (const item of ints7) {
  console.log(item, 'item');
}

不支持

concat
pop
push
shift
splice
unshift

set 方法向定型数组复制前 4 个值,若传第二个参数,则从索引 4 开始复制

// set方法
const container = new Int16Array(8)
container.set(Int16Array.of(1,2,3,4))
console.log(container, 'container');
container.set(Int16Array.of(5, 6, 7, 8), 4)
console.log(container, 'container');

subArray 拷贝一个新的数组,可以接受拷贝指定数组

const container3 = container.subarray()
console.log(container3, 'container');
const container4 = container.subarray(3, 5)
console.log(container4, 'container');

原生拼接定型数组

function typeArrayConcat (typedArrayConstructor, ...typedArrays) {
  const numElements = typedArrays.reduce((x, y) => {
    return (x.length || x) + y.length
  })

  const resultArray = new typedArrayConstructor(numElements)

  let currentOffset = 0;
  typedArrays.map((x) => {
    resultArray.set(x, currentOffset)
    currentOffset += x.length
  })

  return resultArray
}
const container5 = typeArrayConcat(Int32Array, Int8Array.of(1, 2, 3), Int16Array.of(4,5,6), Float32Array.of(7,8,9))
console.log(container5, 'container5');
console.log(container5 instanceof Int32Array, 'container5 instanceof Int32Array');

下溢和上溢

const ints8 = new Int8Array(2)
const unsignedInts = new Uint8Array(2)

索引只取最低有效位,256 超过了 255,无符号整数截断为 0。256 的 8 进制是 100000000,所以截断后前面的 1 就没了,就只有后面的 0

unsignedInts[1] = 256 // [0, 0]
console.log(unsignedInts, 'unsignedInts');

511 的二进制是 111111111(9 位),截取后保留后面的 8 个 1,就是 255

unsignedInts[1] = 511 // [0, 255]
console.log(unsignedInts, 'unsignedInts');

无符号整数不能表示负数,-1 的二进制补码是 11111111,解释为 255。注意,负数补码-1,先求 1 的二进制,是 00000001,然后取反,是 11111110,然后+1,就是 11111110 + 00000001 就是 11111111

// 下溢的位会被转换为其无符号的等价值
unsignedInts[1] = -1
console.log(unsignedInts, 'unsignedInts'); // [0, 255]

有符号整数,赋值为 128,超出了 127,那么求其二补数,128 其二进制是 10000000,取反,再加 1,就是-128

// 上溢自动变成二补数的形式
ints8[1] = 128
console.log(ints8, 'ints8'); // [0, -128]
1000 0000取反
0111 1111
0000 0001 
--加法
1000 0000,最后的1+1等于0,进位1到前面,1+0+1就是0,最后的0+0+1就是1

下溢自动变成二补数的形式

// 下溢自动变成二补数的形式
ints8[1] = 255
console.log(ints8, 'ints8'); // [0, -1]
255的二进制是 1111 1111 => 最高位是1,再有符号整数中,他被解释为负数
有符号整数,负数使用补码表示法
取反就是 0000 0000
+1 就是 0000 0001
因为255的最高位是负数,所以最终要取-1

6.4 Map

map 的遍历方法

keys() values() entries(),map 以插入元素顺序先后作为遍历顺序,object 是没有顺序的

// 遍历key
for (let key of map.keys()) {
  console.log(key, 'key');
}

// 遍历value
for (let value of map.values()) {
  console.log(value, 'value');
}

// 遍历entries
for (let [key, value] of map.entries()) {
  console.log(key, 'key');
  console.log(value, 'value');
}

使用解构遍历

for (let [key, value] of map) {
  console.log(key, 'key');
  console.log(value, 'value');
}

转化为对应数组

const keys  = [...map.keys()]
const values  = [...map.values()]
const arr  = [...map.entries()]
const arr2  = [...map]
console.log(keys, 'keys');
console.log(values, 'values');
console.log(arr, 'arr');
console.log(arr2, 'arr2 47');

结合数组的方法生成新 map

// 结合数组的filter方法生成新的map
const map1 = new Map([...map].filter((v, k) => k < 2))
console.log(map1, 'map1');

// 结合数组的map方法生成新的map
const map2 = new Map([...map].map((k, v) => [v * 2, '_' + k]))
console.log(map2, 'map2');

map 可以直接用 forEach

// map数据结构有forEach方法,可以用来实现遍历
const obj = {
  name: '1', 
  fn () {console.log(1)}
}
// map第二个参数用来指定this,this就是obj,注意,这里不能是箭头函数,否则this是window
map.forEach(function (value, key, m) {
  console.log(value, key, m, 'item, index, m');
  this.fn()
}, obj)

map 的属性和方法

size 属性

// size属性
const map = new Map()
map.set('a', 1)
map.set('b', 2)
console.log(map, 'a');
console.log(map.size, 'a'); // 2

map 的 set 方法

// set方法 给键设置值
// 可以链式写
// 同一个键会更新值
// 返回是map对象
console.log(map.set('c',3).set('a', 111).set('b', 222));

map 的 get 方法

// get方法 找到某个键对应的值
// 找不到键返回undefined
console.log(map.get('a'), 'a'); // 111
console.log(map.get('bb'), 'a'); // undefined

has 方法

// has方法 判断某个键是否在map中
console.log(map.has('a')); // true
console.log(map.has('aaaa')); // false

map 的 delete 方法

// delete方法 删除某个键 删除成功返回true 否则返回false
console.log(map.delete('a'), 'delete a');
console.log(map.delete('aaaa'), 'delete a');

clear 方法

// clear方法 清除所有成员 没有返回值
console.log(map.clear(), 'map.clear()'); // undefined
console.log(map, 'map');
console.log(map.size, 'map'); // 0

map 转化为其他数据结构

数组转 map

// 数组转为map
const map = new Map([
  ['a', 1],
  ['b', 2]
])
console.log(map, 'map');

map 转数组 用扩展运算符

const arr = [...map]
console.log(arr, 'arr');

map 转对象

let obj = {}
map.forEach((value, key, m) => {
  obj[key] = value;
})
console.log(obj, 'obj');

若 map 的键都是字符串,可以无损的转为对象

// 所有map的键都是字符串,可以无损的转化为对象
function strMapToObj (strMap) {
  let obj = Object.create(null)
  for (let [k, v] of strMap) {
    obj[k] = v
  }
  return obj;
}
const map2 = new Map([
  ['a', 1],
  ['b', 2]
])
let obj2 = strMapToObj(map2)
console.log(obj2, 'obj2');

对象转化为 map,Object.entries,将对象转化为二维数组,键值对分别对应数组的 0 和 1 的值

// 对象转化为map
let obj3 = {a: 1, b: 2}
// Object.entries(obj3)返回的是所有可遍历属性的键值对数组,二维(忽略属性是Symbol的情况)
let map3 = new Map(Object.entries(obj3)) // new Map([[a, 1], [b, 2]])
console.log(map3, 'map3');

封装对象转化为 map 的函数

// 自己实现转化的函数
function objToMap(obj) {
  let map = new Map()
  for (let key in obj) {
    map.set(key, obj[key])
  }
  return map
}
console.log(objToMap(obj3), 'objToMap(obj3)');

map 转化为 json,map 键都是字符串

// map转化为对象json-如果map的键都是字符串,可以直接转化为对象json
function strMapToJson(map) {
  return JSON.stringify(strMapToObj(map))
}
let map5 = new Map().set('true', 1).set('false', 2)
console.log(strMapToJson(map5), 'map5');

map 转化为 json,map 的键包含非字符串,如下有 true 和 false 布尔值

function mapToArrayJson(map) {
  // ...map.entries()等同于...map
  return JSON.stringify([...map.entries()])
}
// map转化为数组json-map的key里面包含了非字符串
let map6 = new Map().set(false, 7).set(true, 5)
console.log(mapToArrayJson(map6), 'map6');

json 转化为 map,都是字符串键

// json转化为map-键都是字符串
function jsonToStrMap(jsonStr) {
  return objToMap(JSON.parse(jsonStr))
}
console.log(jsonToStrMap('{"yes": true, "no": false}'));

json 转化为 map,有非字符串键

// json转化为map 整个json就是一个数组 每个成员本身 又是一个有两个成员的数组
function jsonArrToMap(jsonArr) {
  return new Map(JSON.parse(jsonArr))
}
console.log(jsonArrToMap('[[true,7],[{"foo":3},["abc"]]]'), 'jsonArrToMap');

Object 和 Map 比较:

  1. 内存占用:固定大小,Map 大约可以比 object 多存 50%的键值对
  2. 插入性能:插入 Map 在所有浏览器稍微快一点
  3. 查找速度:差异极小,如果只包含少量键值对,Object 更快
  4. 删除性能:涉及大量删除,选 Map 更好,Map 的删除操作比查找和插入更快。Object 的删除 delete 操作饱受诟病

6.5 WeakMap

与 Map 的相同点与区别

WeakMap和map 在存储键值对上类似

可以使用 set 语法

// 可以用set
let wMap = new WeakMap();
let k1 = {obj: 2}
wMap.set(k1, 222)
console.log(wMap, 'wMap');
console.log(wMap.get(k1), 'get k1'); // 222

键可以是数组

// 键可以是数组
let k2 = [1, 2, 3]
wMap.set(k2, '333')
console.log(wMap, 'wMap 22');
console.log(wMap.get(k2), 'wMap 22');

键不能是字符串

// 键不能是字符串
let k3 = '111'
let wMap2 = new WeakMap();
wMap2.set(k3, 222) // 报错 是invalid的语法

symbol 语法可以

wMap2.set(Symbol(), 20)
console.log(wMap2, 'wMap2');

WeakMap所指向的对象不计入垃圾回收机制, 其他地方对element对象的引用一旦消失,这里保存的key也会消失

let element = {name: '1'}
let map2 = new WeakMap()
map2.set(element, '22')
element = null
console.log(map2.has(element), 'map2.has(element)'); // false

弱引用的只是键,值依然还是强引用,所以如下,has 方法调用返回是 false,get 调用返回是 true,能拿到值!注意,值一定是被引用的,才会存在,如果只是一个字符串,wm.set(key, '1')这样可不行,

const wm = new WeakMap()
let key = {}
let obj = { foo: 1 }
wm.set(key, obj)
obj = null // 取消obj变量对该对象的引用
console.log(wm.has(key), 'wm.has()'); // has语法是false
console.log(wm.get(key), 'wm.get()'); // 依然能够拿到这个变量的值

和 map 一样的地方

/* 下面四个方法都有,其它都没有 */
console.log(wm.get);
console.log(wm.set);
console.log(wm.delete);
console.log(wm.has);

其他方法都没了

/* 其它的呢 */
console.log(wm.entries); // undefined
console.log(wm.keys); // undefined
console.log(wm.values); // undefined
console.log(wm.size); // undefined
console.log(wm.forEach); // undefined
console.log(wm.clear); // undefined

用途

dom 节点作为键名

let element = document.getElementById('element')
let map2 = new WeakMap()
map2.set(element, '22')
element.addEventListener('click', function () {
  // ……
})
element = null
console.log(map2.has(element), 'map2.has(element)'); // false

部署私有属性, _counter 和 _action 被删除掉了,类里面的内部属性 counter 和 action 也会随之删除

const _counter = new WeakMap()
const _action = new WeakMap()

class Countdown {
  constructor (counter, action) {
    _counter.set(this, counter)
    _action.set(this, action)
  }
  dec () {
    let counter = _counter.get(this)
    if (counter < 1) return
    counter--
    _counter.set(this, counter)
    if (counter === 0) {
      _action.get(this)()
    }
  }
}
const c = new Countdown(2, () => {console.log('done')})
c.dec()
c.dec()

私有属性示例补充,如下能够拿到私有变量,很容易,只需要拿到 user 实例,并且访问到实例里面的 idProperty

const wm = new WeakMap()
class User {
  constructor (id) {
    this.idProperty = Symbol('id')
    this.setId(id)
  }

  setPrivate (property, value) {
    debugger
    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)
console.log(user.getId(), 'user.getId()'); // 123
user.setId(456)
console.log(user.getId(), 'user.getId()');
// 拿到私有变量
console.log(wm.get(user)[user.idProperty]); // 456

若放到立即执行函数里面,外部就访问不到了

    // 修改后
    const User2 = (() => {
      const wm = new WeakMap()
      class User {
        constructor (id) {
          this.idProperty = Symbol('id')
          this.setId(id)
        }

        setPrivate (property, value) {
          debugger
          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)
        }
      }
    })()                                         

WeakRef

ES2021 提供,如下直接基于 target 原始对象,创建一个 WeakRef 对象实例,该实例对于 target 是弱引用,不会影响垃圾回收机制对该 target 的回收

let target = {
  name: 1
}
let wr = new WeakRef(target)
console.log(wr, 'wr');

deref 判断原始对象是否被清除;如果能够拿到对象,则返回指定的对象 target,拿不到,返回空对象

console.log(wr.deref(), 'wr');

注意,一旦创建了 WeakRef,该对象会在下一轮事件循环默认被清除,不会在本轮事件循环末尾被清除

如下方法,cache 是 Map,但是他的值是 WeakRef(fresh),保持对原始对象的弱引用

function makeWeakCached(f) {
  const cache = new Map();
  return key => {
    const ref = cache.get(key);
    if (ref) {
      const cached = ref.deref();
      if (cached !== undefined) return cached;
    }

    const fresh = f(key);
    cache.set(key, new WeakRef(fresh));
    return fresh;
  };
}

const getImageCached = makeWeakCached(getImage);

注册表功能

注册表是当垃圾回收机制清除完某个变量,执行一个回调函数。下面是利用FinalizationRegistry 构造函数,创建一个注册表实例,一个回调传入到构造函数中,

const registry = new FinalizationRegistry(heldValue => {
  // ....
});

registry 调用register 方法,第一个参数是侦听的对象,第二个参数是要传入给 heldValue 的情况

registry.register(theObject, "some value");

register 方法传入第三个参数后,就支持取消注册表

registry.register(theObject, "some value", theObject);
// ...其他操作...
registry.unregister(theObject);

6.6 Set

set 基本用法

给 new Set 创建实例, add 添加,并 用 for …… of 进行遍历

// 记得 new Set之后加分号
const s = new Set();
[2, 2, 2, 2, 3, 4, 5].forEach(item => s.add(item))
console.log(s, 's');

for (let i of s) {
  // 值不会出现重复
  console.log(i, 'i'); // 2 3 4 5
}

set可以接受一个数组用来初始化,set 有 size 属性

const s2 = new Set([1,1,1,2,2,2,3,4,5])
console.log(s2, 's2'); // {1,2,3,4,5}
console.log(s2.size, 's2.size'); // 5

set 用来去重

// 去除数组里面的重复成员
console.log([...new Set([1,1,1,2,2,2,3,3,3,])]); // [1,2,3]
// 去除重复的字符串
console.log([...new Set('abbc')].join('')); // abc

向 set 里面添加值,不会进行类型转换,去重会类似判断全等,但是不会出现两个 NaN

// 向set里面添加值时不会进行类型转换
let a = NaN
let b = NaN
console.log([...new Set(['5', 5, a, b])]); // 算法类似=== 是精确相等

两个对象

// 两个对象总是不相等的
let c = {}
let d = {}
// 两个对象
console.log([...new Set([c, d])]); 

set 实例的遍历操作

keys() values() entries(), entries 时的 key 和 value 是一样的

let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
  console.log(item, 'item'); // 挨个打印出来
}
// keys和values返回的内容一致,因为set没有键
for (let item of set.values()) {
  console.log(item, 'item'); // 挨个打印出来
}

// entries返回的是键名和值名,每次输出一组,两个成员完全一致
for (let item of set.entries()) {
  console.log(item, 'item'); // ['red', 'red'] ['green', 'green'] ['blue', 'blue']
}

Set 数据结构有 Symbol.Iterator 属性,可以遍历

  // set自身默认就是可以遍历的
  console.log(Set.prototype[Symbol.iterator] === Set.prototype.values, 'Set.prototype'); // true

Set 有 forEach 方法

// Set结构实例和数组一样,也拥有forEach方法
set.forEach((value, key) => {
  console.log(value, 'value'); // red
  console.log(key, 'key'); // red
})

Set 可以用扩展运算符

// 扩展运算符可以去重 -- 默认内部使用for of
let set1 = new Set(['red', 'green', 'blue', 'red'])
let arr1 = [...set1]
console.log(arr1, 'arr1');

Set 可以使用数组的方法

// set间接使用map
let arr2 = [...new Set([1,2,3])].map(item => item * 2)
let set2 = new Set(arr2)
console.log(set2); // 2 4 6的set

Set 可以间接使用 filter 方法

// set间接使用filter
let arr3 = [...new Set([1,2,3])].filter(item => item <= 2)
console.log(arr3, 'arr3');
let set3 = new Set(arr3)
console.log(set3, 'set3'); // 1 2的set

Set 求并集js

// 并集
let a = new Set([1,2,3])
let b = new Set([2,3,4])
let union = new Set([...a, ...b])
console.log(union, 'union');

Set 求交集

// 交集
let intersect = new Set(
  [...a].filter(item => b.has(item))
)
console.log(intersect, 'intersect'); // 2 3是交集

Set 求差集

// a相对于b的差集
let difference = new Set(
  [...a].filter(item => !b.has(item))
)
console.log(difference, 'difference'); // 1 去除了 2 3

和 Array.from()方法结合

let c = new Set([1,2,3])
c = new Set([...c].map(item => item * 2))
console.log(c, 'c'); // {2, 4, 6}

c = new Set(Array.from(c, val => val * 3))
console.log(c, 'c'); // {6, 12, 18}

Set 的属性和方法

set的构造函数和size属性

let s1 = new Set([1,2,3])
console.log(s1.size, 's1'); // 3

add 方法,支持链式调用

// add
console.log(s1.add(4)); // 返回set结构本身
console.log(s1.add(5).add(6).add(7)); // 连续链式的添加

delete 删除方法

// delete
console.log(s1.delete(3)); // 返回true表示是否删除成功

是否拥有某个成员

// has
console.log(s1.has(4)); // 是否拥有某个成员

Array.from 转化 set

// Array.from可以将set转化为数组(数组去重)
const items = new Set([1, 2, 3, 4, 5, 5, 5, 6])
const array = Array.from(items)
console.log(array, 'array'); // 去重后的数组
console.log([...items], 'array'); // 使用延展运算符

ES2025 新增

union 方法 和 intersection 方法

/* ES2025新增的语法 */
const frontEnd = new Set([
  'JavaScript',
  'HTML',
  'CSS'
])
const backEnd = new Set([
  'Python',
  'Java',
  'JavaScript'
])
const unionSet = frontEnd.union(backEnd)
console.log(unionSet, 'unionSet');
const intersectionSet = frontEnd.intersection(backEnd)
console.log(intersectionSet, 'intersectionSet');

difference,求差集,

// 差集运算 第一个集合存在, 第二个集合不存在
const onlyFront = frontEnd.difference(backEnd)
const onlyEnd = backEnd.difference(frontEnd)
console.log(onlyFront, 'onlyFront');
console.log(onlyEnd, 'onlyEnd');

对称差集,返回两个集合中所有独一无二的数,如下 HTML CSS 和 Python Java 是各自里面的独一无二的数

// 对称差集 返回两个集合中所有独一无二的成员
const symFrontEnd = frontEnd.symmetricDifference(backEnd)
console.log(symFrontEnd, 'symFrontEnd');
const symBackEnd = backEnd.symmetricDifference(frontEnd)
console.log(symFrontEnd, 'symFrontEnd');
console.log(symBackEnd, 'symBackEnd');

判断 A 是否是 B 的子集,注意,任何数都是自己的子集

// 判断子集, 返回布尔值
const fronEnd2 = new Set([
  'JavaScript',
  'HTML',
  'CSS'
])
const fronEnd3 = new Set([
  'HTML',
  'CSS'
])
console.log(fronEnd3.isSubsetOf(fronEnd2), 'fronEnd3.isSubsetOf(fronEnd2)'); // true
console.log(fronEnd2.isSubsetOf(fronEnd3), 'fronEnd2.isSubsetOf(fronEnd3)'); // false
// 自己是自己的子集
console.log(fronEnd2.isSubsetOf(fronEnd2), 'fronEnd2.isSubsetOf(fronEnd2)'); // true

判断 A 是否是 B 的超集

const fronEnd2 = new Set([
  'JavaScript',
  'HTML',
  'CSS'
])
const fronEnd3 = new Set([
  'HTML',
  'CSS'
])
// 超集
console.log(fronEnd2.isSupersetOf(fronEnd3), 'fronEnd2.isSuperSetOf(fronEnd3)'); // true
console.log(fronEnd3.isSupersetOf(fronEnd2), 'frontEnd3.isSuperSetOf(fronEnd2)'); // false

判断两个集合是否不相交

// 判断子集, 返回布尔值
const fronEnd2 = new Set([
  'JavaScript',
  'HTML',
  'CSS'
])
const fronEnd3 = new Set([
  'HTML',
  'CSS'
])
const backEnd2 = new Set([
  'Python',
  'Java',
  'JavaScript'
])
// 两个集合是否不相交
console.log(fronEnd3.isDisjointFrom(fronEnd2), 'frontEnd3.isSuperSetOf(fronEnd2)'); // false
console.log(fronEnd3.isDisjointFrom(backEnd2), 'frontEnd3.isSuperSetOf(backEnd2)'); // true

6.7 WeakSet

和 set 的区别:

区别 1: 值必须是对象或者symbol的值,不能是其他类型的值

let set1 = new WeakSet();
set1.add(Symbol()) // 不报错 但是
set1.add(1) // 报错

这样也是错的,因为会把1,2,3当做成员添加给ws

let ws1 = new WeakSet([1,2,3])

二维数组可以

let a = [[1,2,3], [4,5,6]] // [1,2,3][4,5,6]是成员添加给a了
// let b = [1,2,3] // 这样添加是错误的
let ws = new WeakSet(a)
console.log(ws, 'ws');

区别 2:如果其它对象都不再引用该对象,垃圾回收机制会释放掉这个对象,weakSet里面引用或者不引用不重要了。has 方法打印是 false,表示垃圾回收已经清除他了

let obj = {
  name: '123',
}
let set2 = new WeakSet()
set2.add(obj)
console.log(set2, 'set2');
obj = null
console.log(set2, 'set2 释放了吗'); // 手动按一下浏览器的垃圾回收按钮试试,就是被回收了
console.log(set2.has(obj), 'set2.has(obj)'); // 如果obj = null, 那么这里打印的就是false,

区别 3:不可遍历,会报错。因为他不稳定呀!

for (const item of set2) {
  console.log(item, 'item');
}

用途:

给对应的按钮打上标签,表示他已经要禁用。如果用 Set,则按钮被 dom 树消除了,这个引用会还在

let button = document.querySelector('#button)
let ws = new WeakSet()
ws.add(button)