前言
本文按照JS红宝书第六章集合引用类型的顺序整理。看完这篇文章,你将能回答如下的问题:
问:
- 创建数组的方式?【6.2 1】
- new Array(),只传数字和只传字符串有区别吗?【6.2 1】
- 不使用 new 关键字能否执行【6.2 1】
- Array.from()使用场景是什么?【6.2 1】
- Array.from()的几个参数分别是干什么的?【6.2 1】
- Array.of 的使用场景是什么?最早有哪个方法是跟他作用类似的?【6.2 1】
- instanceof和Array.isArray的区别【6.2 4】
- 说说数组的迭代器方法?【6.2 5】
- 数组一共有哪些方法,能分类说说吗?数组有哪些排序方法,会改变数组本身吗?【6.2 10】
- 数组的fill和copyWithin方法区别?【6.2 6】
- 数组执行toString valueOf toLocaleString方法,返回值有什么不同?【6.2 7】
- join方法默认是用什么进行分割?【6.2 7】
- 数组的splice方法有哪些用途?【6.2 11】
- 了解数组的定型数组吗?了解ArrayBuffer吗?了解Int32Array构造函数吗?【6.3】
- map有哪些属性和方法?set有哪些属性和方法?【6.4 6.6】
- 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
这两个检测数组方式区别:
- 机制不同:instanceof 是用于判断这个实例在某个构造函数的原型链上,Array.isArray 只能检测数组
- 跨上下文: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 新增的结构,目的=>提升向原生库传输数据的效率
- WebGL
早期,JS 和 WebGL 之间运行传输数据时存在性能问题,JS 在内存中的双精度浮点值的格式和 WebGL 中的格式不匹配
- 定型数组
所以出现了定型数组,增加 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()
注意点:
- 分配失败直接报错
- 分配最大内存不超过 2 ^ 53 - 1
- 声明 ArrayBuffer 会将所有二进制初始化为 0
- 分配的堆内存可以被当成垃圾回收,不用手动释放
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 比较:
- 内存占用:固定大小,Map 大约可以比 object 多存 50%的键值对
- 插入性能:插入 Map 在所有浏览器稍微快一点
- 查找速度:差异极小,如果只包含少量键值对,Object 更快
- 删除性能:涉及大量删除,选 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)