全面解析Set,WeakSet,Map及强弱引用面试考题

220 阅读6分钟

前言

ES6,即ECMAScript 2015,是JavaScript的一个新标准,引入了许多新的特性和语法,旨在提高开发效率和代码质量。从更简洁的箭头函数到强大的模块系统,ES6为现代Web开发带来了革命性的变化。与此同时,也带来了更多的面试重点,从本篇文章开始我们将逐步解析ES6,全面认识ES6,通过简单易懂的方式拿下其中面试重点。

一,Set

有以下几个基本性质:

  1. 唯一性Set 对象中的元素是唯一的,每个元素只会出现一次。如果尝试添加已存在的元素,Set 不会改变。
  2. 无序性Set 对象中的元素是无序的,即元素的插入顺序和迭代顺序可能不同。
  3. 可迭代性Set 对象是可迭代的,因此可以使用 for...of 循环来遍历其元素。

1.基本方法

  • size:返回元素的个数
const s = new Set([1, 2, 3, 4])

console.log(s.size);//4
  • add(value):添加元素,返回 Set 对象本身
const s = new Set([1, 2, 3, 4])

s.add(5)
console.log(s);
//Set(5) { 1, 2, 3, 4, 5 }
  • delete(value):删除某个元素
const s = new Set([1, 2, 3, 4])

s.delete(1)
console.log(s);//Set(3) { 2, 3, 4 }
  • has(value):返回一个布尔值,表示某个值是否存在
const s = new Set([1, 2, 3, 4])

console.log(s.has(1));//true
  • clear():清除所有元素
const s = new Set([1, 2, 3, 4])

console.log(s.clear(s));//undefined

2.除重方法

a,数组除重

1.new Set(arr))

将原数组转变为一个Set对象,利用Set对象的唯一性----在JS中,Set 对象是一个值的集合,每个值在Set中只会出现一次。因此,当你创建一个Set并传入一个包含重复元素的数组时,重复的元素会被自动去重。

const arr = [1, 2, 3, 4, 4, 3, 2, 1]

console.log(new Set(arr))
//Set(4) { 1, 2, 3, 4 }

2.解构[...new Set(arr)] 将原数组转变为Set对象这一步显然不能达到我们的目标,因为他返回的是一个 Set 对象,我们还需要将这个对象解构然后放到一个新数组中

const arr = [1, 2, 3, 4, 4, 3, 2, 1]

const arr2 = [...new Set(arr)]

console.log(arr2);
//[ 1, 2, 3, 4 ]

所以我们可以总结以下三步:

  • 将原数组转变为Set对象,去重
  • 将Set对象解构,并转换为数组(...,[]
  • 将新的数组赋值给新的变量

b,字符串除重

1.[...new Set(str)].join('')

对于字符串来说,前几个步骤是一样的

  • 将原数组转变为Set对象,去重
  • 将Set对象解构,并转换为数组(...,[]);此时得到[ 'a', 'b', 'c' ]
  • join('') 方法将数组中的元素连接成一个新的字符串;得到abc
  • 将新的数组赋值给新的变量
const str = 'abcabc'

const str2 = [...new Set(str)].join('')
console.log(str2);
//abc

3.遍历方法

1.通过for of遍历

const s = new Set([1,2,3,3]) // 重复的3会被自动去重 

for(let item of s){
console.log(item);
}
//1
//2
//3

如果直接用for遍历?

可以通过先将Set转换为数组,然后再进行遍历。但是请注意,这样做会失去Set的一些特性,比如元素的唯一性保证。

const s = new Set([1, 2, 3, 3]);

const arrayFromSet = Array.from(s); // 或者 [...s] 使用扩展运算符

for (let i = 0; i < arrayFromSet.length; i++) { console.log(arrayFromSet[i]); }

2.使用本身存在的keys()、values()、entries() 和 forEach()四种遍历方法,

keys()values() (效果相同)

一次遍历

const s = new Set([1, 2, 3, 3]); 

console.log(s.keys());
//console.log(s.values());

屏幕截图 2024-05-28 020400.png 二次遍历

const s = new Set([1, 2, 3, 3]); 

for (let key of s.keys()) {
console.log(key); 
} 

//for (let value of s.values()) {
//console.log(value);
//} 

屏幕截图 2024-05-27 211430.png

entries()

一次遍历

const s = new Set([1, 2, 3, 3]);

console.log(s.entries());

屏幕截图 2024-05-28 020954.png

二次遍历

const s = new Set([1, 2, 3, 3])

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

屏幕截图 2024-05-27 210630.png

forEach()

const s = new Set([1, 2, 3, 3])

s.forEach(console.log);

屏幕截图 2024-05-27 210508.png

二,强引用和弱引用

弱引用(Weak References)和强引用(Strong References)在编程中是对对象引用方式的两种不同分类,它们的主要区别在于对垃圾回收器(Garbage Collector)的影响以及对象的生命周期管理

垃圾回收机制的算法不受人类掌控,可能突然生效又突然被清理

区别总结

  • 稳定性:强引用是稳定的,只要引用存在,对象就不会被回收;而弱引用是非稳定的,对象可能被随时回收。
  • 垃圾回收:强引用会阻止垃圾回收器回收对象;而弱引用不会阻止,只要没有其他强引用指向对象,对象就可能被回收。
  • 用途:强引用是日常编程中常用的引用方式;而弱引用通常用于实现缓存(如LRU缓存),以避免内存泄漏,或者用于观察对象是否被垃圾回收(例如,在调试和诊断工具中)。
  • 生命周期管理:强引用直接管理对象的生命周期;而弱引用则是一种更间接、更灵活的管理方式。

三,weakSet

  • 由于WeakSet中的引用是弱引用,因此它不适合用于需要长期存储引用的场景。
  • WeakSet没有提供遍历方法,因此无法直接获取其包含的所有元素。
  • WeakSet在初始化时不能通过赋值来填充,只能通过add方法逐个添加元素

四,面试

面试题一:set和weakset的区别

  • Set:对元素的引用是强引用,即Set中的元素在Set存在期间不会被垃圾回收机制回收。
  • WeakSet:对元素的引用是弱引用。如果没有其他对WeakSet中对象的引用存在,那么这些对象会被垃圾回收。即WeakSet不会阻止垃圾回收。

面试题二:结合弱引用的垃圾回收原理详细说明

  1. 一个对象obj存放在其他的结构中,当后续存在其他对象引用这个对象,那么这个对象的内存就不会被回收
  2. 一个对象obj存在其他结构中,当后续只存在WeakSet对他的引用,该对象的内存依然会被回收

适用场景:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="wrap">
        <button id="btn">确定
    </div>
    </div>
    <script>
        let wrap = document.getElementById("wrap")
        let btn = document.getElementById("btn")

        //给按钮打上禁用标签
        const disabledEls = new Set()//new WeakSet
        disabledEls.add(btn)
        //按钮点击就消失
        btn.addEventListener("click", () => {
            wrap.removeChild(btn)
        })

    </script>
</body>
</html>

在以上代码中,按钮被点击就会消失,btn不会再被引用但。但是第20行使用的是Set()强引用,当代码执行完毕时第17行这一无用代码的Dom结构会一直留在内存中,我们可以将第20行的Set()强引用变为WeakSet()弱引用,WeakSet()会给17行打上null标签,当代码执行完成后垃圾回收机制会第一时间清理他。

五,map

可以使用任意的数据类型作为key,弥补了传统对象只能使用字符串做key的缺陷

基本方法

  • set(key, value):设置 Map 中某个键的值,返回 Map 对象本身
  • get(key):返回 Map 中某个键对应的值,如果该键不存在则返回 undefined

同样具有set的方法:

  • size:返回 Map 中键值对的个数
  • has(key):返回一个布尔值,表示某个键是否存在于 Map 中
  • delete(key):删除 Map 中的某个键值对,返回一个布尔值表示删除是否成功
  • clear():清除 Map 中的所有键值对

遍历方法,和set一样

四种遍历方法: keys()values()entries() 和 forEach()

const m = new Map();
const o = { age: 18 }
m.set(o, [1, 2, 3])

console.log(m.keys());
//console.log(m.values());
//console.log(m.entries());
//console.log(m.forEach());

以下是对应输出结果: 屏幕截图 2024-05-27 214540.png