前端面试复习8--- ES6

143 阅读20分钟

ES6

一、解构赋值

解构赋值比较简单也是日常最常用的没什么好说,直接写栗子

普通的解构

const obj = { aaa: 1, bbb: 2 }
const { aaa, bbb } = obj

const list = [1, 2]
const [ccc, dddd] = list

多层解构

const obj = { aaa: { ccc: 1 }, bbb: 2 }
const {
  aaa: { ccc },
  bbb,
} = obj

解构别名

const school = {
  name: 'xxx',
  teacher: {
    name: 'yyy',
  },
}

const {
  name: schoolName, // 别名
  teacher: { name },
} = school

解构结合初始值

const course = ({ teacher, course = 'xxx' }) => {
  console.log(teacher, course)
}

course({
  teacher: 'yyy',
})

变量交换

let a = 1
let b = 2
;[b, a] = [a, b]

二、let const

块级作用域

if (1) {
  let aaa = 1
}
console.log(aaa) // aaa is not defined

块级作用域可以避免变量共享问题,这在需要在循环中使用定时器或异步操作时非常有用。

for (var index = 0; index < 9; index++) {
  setTimeout(() => {
    console.log(index)
  })
}
// 打印出来 9 个 9

for (let index = 0; index < 9; index++) {
  setTimeout(() => {
    console.log(index)
  })
}
// 打印出来 0-8

var 时 index 是共享的,当同步代码执行完的时候它已经被改成 9 了,所以再执行异步代码打印就都是 9 了。 let声明具有块级作用域,每次迭代都会创建一个新的块级作用域和新的 index,并且变量 index的值在每个迭代中都会被自动更新。所以打印出来 0-8。

类似有 9 个这样的作用域:

{
  let index = 0
  setTimeout(() => {
    console.log(index)
  })
}

{
  let index = 1
  setTimeout(() => {
    console.log(index)
  })
}

// ···

let const 的变量提升和临时死域

let const 声明的变量是否会被提升?我的答案是:会的,但是会有临时死域。

先看一个 var 的示例:

// 全局作用域
x = 'global'
;(function () {
  // 函数作用域
  console.log(x)
  var x = 1
})()

这个栗子很容易看得出来,var 声明的 x 在函数作用域内已经被提升了,所以 console.log(x)undefined,假如没有被提升应该会顺着作用域链找到 x = 'global',当然这个假如是不存在的。

现在把 var 换成 letconst

// 全局作用域
x = 'global'
;(function () {
  // 函数作用域
  console.log(x)
  const x = 1
})()
  • console.log(x) 打印出来的不是 global,而是控制台报错 Uncaught ReferenceError: Cannot access 'x' before initialization
  • 说明 const 声明的 x 已经被提升了,只是存在临时死域,如果没有被提升应该会顺着作用域链向上查找到 x = 'global'

什么是临时死域?

  • let/const 声明的那行到作用域顶端就是临时死域。let const 声明时分两步,创建初始化
  • 变量在这个区间只是被创建还没初始化,不能被使用即创建被提升初始化没有被提升,所以这个区间被成为临时死域
  • var 声明的创建初始化都被提升了,初始化为 undefined,用的时候再赋值。
  • 另外 function 声明的创建初始化也都被提升了,初始化就是为它一开始的定义的整个函数体。
let x = 'global'
{
  // 作用域顶端 x 的临时死域--start

  console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization

  // x 的临时死域--end
  let x = 1
}

const 常量,引用类型的内部元素是可以被改变的

在实际编码中就是引用类型且内部元素可能要被改变也建议用 const 因为这样后面维护的时候很容易知道这个那些变量是不会被重新赋值了

如果是引用类型的变量,同时也不想内部元素被修改可以用 Object.freeze()

const aaa = {
  bbb: '123',
}

Object.freeze(aaa)

aaa.bbb = '456'

console.log(aaa.bbb) // 123

Object.freeze() 只能冻结根层,嵌套引用类型不能完全冻结,手写一个递归方法,冻结所有层级

Object.freezeAll = (obj) => {
  Object.freeze(obj)
  ;(Object.keys(obj) || []).forEach((key) => {
    if (typeof obj[key] === 'object') {
      Object.freezeAll(obj[key])
    }
  })
}

const aaa = {
  bbb: { ccc: { ddd: 123 } },
}

Object.freezeAll(aaa)

aaa.bbb.ccc.ddd = 456

console.log(aaa.bbb.ccc.ddd) // 123

es5 中实现常量

Object.defineProperty(window, 'bbb', {
  value: { ccc: '098' },
  writable: false,
})

bbb = 456

console.log(bbb)

Object.defineProperty()writable 一样也是只能冻结根层

三、箭头函数

箭头函数没有独立的函数作用域,它的作用域与定义它的上下文相同。这意味着箭头函数继承了父级作用域的 this 值,而不会创建自己的新 this

function Timer() {
  this.seconds = 0

  setInterval(() => {
    // this 指向 Timer 对象
    this.seconds++
    console.log(this.seconds)
  }, 1000)
}

const timer = new Timer()

箭头函数对于编写简洁和清晰的代码非常有用。然而,它也有局限性,例如无法作为构造函数使用,不能绑定自己的 this 值,以及没有 arguments 对象等。因此,在选择使用箭头函数时,需要根据具体情况和需求来判断是否合适。

无法作为构造函数使用

const Person = (name) => {
  this.name = name // 错误!箭头函数没有自己的 this 绑定
}

const person = new Person('John') // 错误!箭头函数不能作为构造函数使用
  • 箭头函数没有自己的 this,它继承父级作用域的 this,在 new 一个实例的时候, 无法将 this 指向 new 的实例。
  • 同时箭头函数没有 prototype 属性,实例对象也没法通过原型链找到箭头函数的 prototype,实现继承。
  • 小总结一下,箭头函数的 this 值在定义时确定,并且无法被修改。

不能绑定自己的 this

栗子一

const obj = {
  name: 'yy',
  showName: () => {
    console.log(this.name) // undefined
  },
}
obj.showName()

栗子二

const aaa = () => {
  console.log(this.ccc) // undefined
}

const bbb = {
  ccc: 123,
}

aaa.apply(bbb)

没有 arguments 对象

const aaaFn = (bbb) => {
  console.log(arguments) // Uncaught ReferenceError: arguments is not defined
}

aaaFn(123)

四、ES6 中数组的遍历

ES6 提供了一些方便遍历数组的方法,让我们逐一介绍它们:

  1. forEach():对数组中的每个元素执行提供的回调函数。
array.forEach((element, index, array) => {
  // 处理每个元素
})
  1. map():通过对数组中的每个元素调用提供的函数,创建一个新数组,包含返回的结果。
const newArray = array.map((element, index, array) => {
  // 返回处理后的元素
})
  1. filter():创建一个新数组,包含满足回调函数条件的所有元素。
const newArray = array.filter((element, index, array) => {
  // 返回满足条件的元素
})
  1. find():返回数组中满足提供的测试函数的第一个元素值,否则返回 undefined
const foundElement = array.find((element, index, array) => {
  // 返回满足条件的第一个元素
})
  1. findIndex():返回数组中满足提供的测试函数的第一个元素的索引,否则返回 -1。
const foundIndex = array.findIndex((element, index, array) => {
  // 返回满足条件的第一个元素的索引
})
  1. reduce():通过提供的回调函数,将数组中的元素逐个累积为一个值。
const result = array.reduce((accumulator, element, index, array) => {
  // 根据回调函数逐步累积值
  return accumulatedValue
}, initialValue)

与此相像的还有 reduceRight()

  1. some():检查数组中是否至少有一个元素满足提供的测试函数,返回一个布尔值。(一真即真)
const hasSome = array.some((element, index, array) => {
  // 返回测试结果
})
  1. every():检查数组中的所有元素是否都满足提供的测试函数,返回一个布尔值。(一假即假)
const allMatch = array.every((element, index, array) => {
  // 返回测试结果
})

五、Proxy

ES6 引入了 Proxy 对象,它提供了一种机制,可以拦截并自定义对象的底层操作。Proxy 可以用于在对象上定义自定义行为,例如属性访问、属性赋值、函数调用等。

要创建一个 Proxy 对象,你需要传入两个参数:目标对象和一个处理程序对象,该处理程序对象用于定义拦截操作。

const proxy = new Proxy(target, handler)
  • target 是要应用代理的目标对象。
  • handler 是一个处理程序对象,它包含了用于定义拦截操作的方法。

这里是一些常见的拦截操作方法:

  • get(target, property, receiver): 当访问对象的属性时触发。可以拦截属性的读取操作并返回自定义值。
  • set(target, property, value, receiver): 当设置对象的属性时触发。可以拦截属性的赋值操作并执行自定义操作。
  • apply(target, thisArg, argumentsList): 当调用函数时触发。可以拦截函数的调用并执行自定义操作。
  • has(target, property): 当使用 in 操作符检查属性是否存在时触发。可以拦截对属性的存在检查操作。
  • deleteProperty(target, property): 当删除对象的属性时触发。可以拦截属性的删除操作。
  • getOwnPropertyDescriptor(target, property): 当获取对象属性的描述符时触发。可以拦截对属性描述符的访问操作。

get set deleteProperty has getOwnPropertyDescriptor

const target = { name: 'Alice', age: 30, test: '' }

const handler = {
  get(target, property, receiver) {
    console.log(`正在访问属性 '${property}'`)
    return target[property]
  },
  set(target, property, value, receiver) {
    console.log(`正在设置属性 '${property}' 为值 '${value}'`)
    target[property] = value
    return true
  },
  deleteProperty(target, property) {
    delete target[property]
    console.log(`正在删除 '${property}' 属性`)
  },
  has(target, property) {
    console.log(`正在检查 '${property}' 是否存在`)
    return property in target
  },
  getOwnPropertyDescriptor(target, property) {
    console.log(`正在获取 '${property}' 的描述信息`)
    return Object.getOwnPropertyDescriptor(target, property)
  },
}

const proxy = new Proxy(target, handler)
console.log(proxy.name) // 输出:正在访问属性 'name', Alice
proxy.age = 31 // 输出:正在设置属性 'age' 为值 '31'
console.log(proxy.age) // 输出:正在访问属性 'age', 31
delete proxy.test // 输出:正在删除 'test' 属性
console.log('name' in proxy) // 输出:正在检查 'name' 是否存在
console.log(Object.getOwnPropertyDescriptor(proxy, 'name')) // 输出:正在获取 'name' 的描述信息

完整输出记录

正在访问属性 'name'
Alice
正在设置属性 'age' 为值 '31'
正在访问属性 'age'
31
正在删除 'test' 属性
正在检查 'name' 是否存在
true
正在获取 'name' 的描述信息
{value: 'Alice', writable: true, enumerable: true, configurable: true}

apply

const targetFunction = function (name) {
  console.log(`Hello, ${name}!`)
}

const proxyFunction = new Proxy(targetFunction, {
  apply(target, thisArg, argumentsList) {
    console.log('Before function call')
    target(...argumentsList)
    console.log('After function call')
  },
})

proxyFunction('Alice')

输出记录

Before function call
Hello, Alice!
After function call

Proxy 一些不常用的方法

  • getPrototypeOf(target): 当访问对象的原型时触发。可以拦截对原型的访问。
  • setPrototypeOf(target, prototype): 当设置对象的原型时触发。可以拦截对原型的更改操作。
  • isExtensible(target): 当检查对象是否可扩展时触发。可以拦截对对象可扩展性的检查操作。
  • preventExtensions(target): 当阻止对象扩展时触发。可以拦截对象的扩展操作。
  • getOwnPropertyNames(target): 当获取对象自身属性的名称数组时触发。可以拦截对属性名称数组的访问操作。
  • defineProperty(target, property, descriptor): 当定义对象的属性时触发。可以拦截对属性的定义操作。
  • ownKeys(target): 当获取对象所有自身属性的键的数组时触发。可以拦截对键数组的访问操作。

六、Reflect

Reflect 方法与对应的 Object 方法功能类似,但具有一些额外的特性。

使用 Reflect 对象有几个原因和优势:

  1. 统一的对象操作方法:Reflect 提供了一组统一的对象操作方法,使得对对象的操作更加一致和直观。这些方法与原生的操作符(比如 .[])相比,具有相似的语法和功能,使我们可以更方便地进行属性的获取、设置、删除等操作。
  2. 可以替代或扩展原生操作:Reflect 方法可以用来替代或补充一些原生对象操作。例如,可以使用 Reflect.getReflect.set 方法来替代传统的属性访问方式,提供更严格的错误处理和更灵活的拦截能力。这使得我们可以根据需要对对象的操作进行自定义。
  3. 更好的错误处理机制:使用 Reflect 方法执行对象操作时,会返回相应的布尔值或结果,而不是抛出异常。这使得错误处理更加简洁和直观,可以更轻松地根据操作结果进行进一步的处理。
  4. 可以和 Proxy 对象结合使用:Reflect 方法与 Proxy 对象紧密相关。Proxy 是 ES6 中提供的一种拦截对象操作的机制,可以对对象的操作进行代理和修改。Reflect 方法提供了一些与 Proxy 对象配合使用的增强功能,可以在 Proxy 对象的拦截器中使用 Reflect 方法完成相应的操作,从而实现更灵活和可控的对象操作。

常用的 Reflect 方法示例

  1. 获取和设置属性值:
const obj = {
  name: 'Alice',
  age: 30,
}

// 使用 Reflect.get 获取属性值
const name = Reflect.get(obj, 'name')
console.log(name) // 输出: Alice

// 使用 Reflect.set 设置属性值
Reflect.set(obj, 'age', 31)
console.log(obj.age) // 输出: 31
  1. 检查属性是否存在:
const obj = {
  name: 'Alice',
  age: 30,
}

// 使用 Reflect.has 检查属性是否存在
const hasName = Reflect.has(obj, 'name')
console.log(hasName) // 输出: true

const hasEmail = Reflect.has(obj, 'email')
console.log(hasEmail) // 输出: false
  1. 删除属性:
const obj = {
  name: 'Alice',
  age: 30,
}

// 使用 Reflect.deleteProperty 删除属性
Reflect.deleteProperty(obj, 'age')
console.log(obj.age) // 输出: undefined
  1. 调用函数:
function greet(name) {
  console.log('Hello, ' + name + '!')
}

// 使用 Reflect.apply 调用函数
Reflect.apply(greet, null, ['Alice']) // 输出: Hello, Alice!
  1. 创建实例:
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

// 使用 Reflect.construct 创建实例
const person = Reflect.construct(Person, ['Alice', 30])
console.log(person instanceof Person) // 输出: true
console.log(person.name) // 输出: Alice
console.log(person.age) // 输出: 30

Proxy 和 Reflect 结合使用

const target = {
  name: 'Alice',
  age: 30,
}

// 创建一个 Proxy 对象
const proxy = new Proxy(target, {
  get(target, propertyKey, receiver) {
    // 使用 Reflect.get 获取属性值
    const value = Reflect.get(target, propertyKey, receiver)
    console.log('Getting ' + propertyKey + ': ' + value)
    return value
  },
  set(target, propertyKey, value, receiver) {
    console.log('Setting ' + propertyKey + ': ' + value)

    // 使用 Reflect.set 设置属性值
    return Reflect.set(target, propertyKey, value, receiver)
  },
  deleteProperty(target, propertyKey) {
    console.log('Deleting ' + propertyKey)

    // 使用 Reflect.deleteProperty 删除属性
    return Reflect.deleteProperty(target, propertyKey)
  },
})

proxy.name // 输出: Getting name: Alice
proxy.age = 31 // 输出: Setting age: 31
delete proxy.age // 输出: Deleting age

七、Set

ES6 中的 Set 是一种数据结构,用于存储一组唯一的值。它类似于数组,但不同的是它只能存储唯一的值,不会出现重复。

使用 new Set() 可以创建一个空的 Set 对象,你也可以通过传入一个可迭代对象(如数组)来初始化 Set 对象。

以下是一些常见的 Set 操作和用法:

  1. 添加元素:使用 add(value) 方法向 Set 中添加新元素。
const set = new Set()
set.add(1)
set.add(2)
set.add(3)
console.log(set) // 输出: Set { 1, 2, 3 }
  1. 删除元素:使用 delete(value) 方法从 Set 中删除指定的元素。
set.delete(2)
console.log(set) // 输出: Set { 1, 3 }
  1. 检查元素是否存在:使用 has(value) 方法检查 Set 中是否存在指定的元素。
console.log(set.has(1)) // 输出: true
console.log(set.has(2)) // 输出: false
  1. 获取 Set 大小:使用 size 属性来获取 Set 的元素个数。
console.log(set.size) // 输出: 2
  1. 迭代 Set 元素:使用 forEach(callback) 方法或者通过 for...of 进行迭代。
// 使用 forEach 迭代
set.forEach((value) => {
  console.log(value) // 输出: 1 3
})

// 使用 for...of 迭代
for (const value of set) {
  console.log(value) // 输出: 1 3
}

Set 还有其他一些方法和特性,比如 clear 方法用于清空 Set,entries 方法用于获取包含 Set 的键值对的迭代器,values 方法用于获取 Set 的值的迭代器等等。

Set 的特性

  1. 唯一性:Set 中的值是唯一的,不会出现重复的元素。当你需要确保集合中的元素不重复时,Set 是一个非常方便的选择。它会自动去重,无需手动判断和处理重复值。
  2. 简单易用的去重机制:向 Set 中添加已存在的元素时,它会自动忽略,无需进行额外的去重操作。
  3. 快速查找:Set 中的值是用哈希存储的,这使得在 Set 中进行值的查找非常高效。相比较而言,传统数组需要使用循环或其他方法进行元素的查找。
  4. 迭代顺序与插入顺序一致:Set 内部维持着元素的插入顺序,也就是说,当你迭代 Set 时,它会按照元素插入的顺序返回值。
  5. 支持迭代器:Set 提供了内置的迭代器,你可以方便地使用 forEach 方法或者通过 for...of 进行遍历。
  6. 可以存储任意类型的值:Set 可以存储任意类型的值,包括原始数据类型(如数字、字符串)、对象、函数等等。

Set 的常用场景

Set 在许多场景下都非常有用。以下是一些常见的应用场景:

  1. 去重:Set 是用于处理唯一值的理想选择。通过将值添加到 Set 中,你可以自动去除重复的值,从而得到一个只包含唯一值的集合。
const array = [1, 2, 2, 3, 4, 4, 5]
const uniqueSet = new Set(array)
console.log([...uniqueSet]) // 输出: [1, 2, 3, 4, 5]
  1. 判断值是否存在:Set 提供了快速的查找功能,你可以轻松地检查一个值是否存在于集合中,而无需遍历整个集合来搜索。
const set = new Set([1, 2, 3, 4, 5])
console.log(set.has(3)) // 输出: true
console.log(set.has(6)) // 输出: false
  1. 数组和集合的转换:你可以使用 Array.from() 方法或扩展操作符 ... 将 Set 转换为数组,或者使用 Set 构造函数将数组转换为 Set。
const set = new Set([1, 2, 3, 4, 5])
const array = Array.from(set)
console.log(array) // 输出: [1, 2, 3, 4, 5]

const set2 = new Set([...array])
console.log(set2) // 输出: Set { 1, 2, 3, 4, 5 }
  1. 集合运算:Set 提供了一些方便的方法来执行集合运算,如求交集、并集和差集等。
const set1 = new Set([1, 2, 3])
const set2 = new Set([2, 3, 4])

// 求交集
const intersection = new Set([...set1].filter((value) => set2.has(value)))
console.log(intersection) // 输出: Set { 2, 3 }

// 求并集
const union = new Set([...set1, ...set2])
console.log(union) // 输出: Set { 1, 2, 3, 4 }

// 求差集
const difference = new Set([...set1].filter((value) => !set2.has(value)))
console.log(difference) // 输出: Set { 1 }
  1. 缓存和数据筛选:Set 可以用于缓存数据或筛选数据集,只保留唯一的值。

八、Map

Map 对象中的键和值可以是任意类型的,并且相对于传统的对象,它具有以下几个优点:

  1. 键的多样性:Map 可以使用任何类型的值作为键,包括原始类型(如字符串、数字)、对象、函数等。而传统对象的键只能是字符串或 Symbol 类型。
  2. 迭代顺序的保证:Map 内部维护着键值对的插入顺序,迭代 Map 的时候会按照插入顺序返回键值对。这对于需要依赖键值对的顺序的场景非常有用。
  3. 更灵活的键值对操作:Map 提供了丰富的 API 来操作键值对,包括添加、获取、修改和删除键值对等。而传统对象的操作相对有限。

下面是一些使用 Map 的例子:

const map = new Map()

// 添加键值对
map.set('key1', 'value1')
map.set(2, 'value2')
map.set({ name: 'John' }, 'value3')

// 获取值
console.log(map.get('key1')) // 输出: value1

// 判断是否存在某个键
console.log(map.has(2)) // 输出: true

// 获取 Map 的大小
console.log(map.size) // 输出: 3

// 删除某个键值对
map.delete('key1')

// 清空 Map
map.clear()

除了上述常用的操作方法外,Map 还提供了 forEachkeysvaluesentries 等方法供遍历和迭代 Map 对象。

Map 常用场景

当涉及到常用场景时,下面是几个使用 ES6 Map 的简单示例:

  1. 数据存储和检索:
const userData = new Map()

// 存储用户信息
userData.set('John', { age: 28, email: 'john@example.com' })
userData.set('Alice', { age: 32, email: 'alice@example.com' })

// 检索用户信息
console.log(userData.get('John')) // 输出: { age: 28, email: "john@example.com" }
  1. 对象属性的映射:
const obj1 = { id: 1, name: 'Apple' }
const obj2 = { id: 2, name: 'Banana' }

const objectMap = new Map()

// 对象属性映射
objectMap.set(obj1, 10)
objectMap.set(obj2, 20)

console.log(objectMap.get(obj1)) // 输出: 10
console.log(objectMap.get(obj2)) // 输出: 20
  1. 字典和单词计数:
const wordCountMap = new Map()

// 单词计数
const sentence = 'I have a pen, I have an apple.'
const words = sentence.split(' ')

words.forEach((word) => {
  const count = wordCountMap.get(word) || 0
  wordCountMap.set(word, count + 1)
})

console.log(wordCountMap.get('I')) // 输出: 2
console.log(wordCountMap.get('apple.')) // 输出: 1
  1. 事件/订阅管理:
const eventMap = new Map()

// 订阅事件
eventMap.set('click', () => {
  console.log('Click event triggered')
})

eventMap.set('hover', () => {
  console.log('Hover event triggered')
})

// 触发事件
eventMap.get('click')() // 输出: Click event triggered
eventMap.get('hover')() // 输出: Hover event triggered

九、Symbol

下面是几个使用 ES6 Symbol 的常用场景的简单示例:

  1. 创建唯一的属性键:
const name = Symbol('name')
const age = Symbol('age')

const person = {
  [name]: 'John',
  [age]: 30,
}

console.log(person[name]) // 输出: John
console.log(person[age]) // 输出: 30
  1. 防止属性名冲突:
// 第三方代码库中的模块A
const myModuleA = {
  uniqueMethod: Symbol('uniqueMethod'),
  // ...
}

// 第三方代码库中的模块B
const myModuleB = {
  uniqueMethod: Symbol('uniqueMethod'),
  // ...
}

// 使用方
console.log(myModuleA.uniqueMethod) // 输出: Symbol(uniqueMethod)
console.log(myModuleB.uniqueMethod) // 输出: Symbol(uniqueMethod)
  1. 定义类的私有成员:
const _name = Symbol('name')
const _age = Symbol('age')

class Person {
  constructor(name, age) {
    this[_name] = name
    this[_age] = age
  }

  getName() {
    return this[_name]
  }

  getAge() {
    return this[_age]
  }
}

const john = new Person('John', 30)
console.log(john.getName()) // 输出: John
console.log(john.getAge()) // 输出: 30
  1. 自定义迭代器和迭代协议:
const myIterable = {
  [Symbol.iterator]() {
    let count = 0

    return {
      next() {
        count++
        if (count <= 5) {
          return { value: count, done: false }
        } else {
          return { value: undefined, done: true }
        }
      },
    }
  },
}

for (let value of myIterable) {
  console.log(value)
}
// 输出:
// 1
// 2
// 3
// 4
// 5

你可以根据需求灵活地运用 Symbol 来确保属性的唯一性、定义私有成员或者自定义对象的行为。

十、weakSet 与 Set 的不同

WeakSet 和 Set 是两个不同的数据结构,它们之间有以下几个主要的区别:

  1. 值类型:

    • Set 可以存储任意类型的值,包括原始值(如字符串、数字等)和对象。
    • WeakSet 只能存储对象类型的值,不能存储原始值。
  2. 引用关系:

    • Set 中的值是强引用关联的,即使没有其他引用指向 Set 中的某个值,该值仍然会存在于 Set 中。
    • WeakSet 中的值是弱引用关联的,如果没有其他引用指向 WeakSet 中的某个值,该值可能会被垃圾回收机制自动清除。
  3. 迭代和大小:

    • Set 是可迭代的,可以使用 forEach 方法或 for...of 循环来遍历 Set 中的值。
    • WeakSet 不可迭代,没有提供直接的迭代方法或属性,也没有 size 属性来获取存储的值的数量。
  4. 键和比较:

    • Set 使用“SameValueZero”算法进行值的比较,即相同的值被视为相等,不会进行类型转换。
    • WeakSet 使用弱引用作为键,只能存储对象类型的值,且只对相同的对象引用进行比较。
  5. 应用场景:

    • Set 适用于需要存储一组唯一值,并且需要对这些值进行迭代或判断某个值是否存在的场景。
    • WeakSet 适用于需要存储对象集合,并且希望在不阻止对象被垃圾回收的情况下存储对象的场景。

需要注意的是,由于 WeakSet 中的值是弱引用关联的,所以在使用 WeakSet 时需要特别小心,确保外部的引用不会被删除,否则可能导致 WeakSet 中的值被意外清除。

十一、weakMap 与 Map 的不同

WeakMap 和 Map 是两个不同的数据结构,它们之间有以下几个主要的区别:

  1. 键类型:

    • Map 可以使用任意类型的值作为键,包括原始值(如字符串、数字等)和对象。
    • WeakMap 只能使用对象类型作为键,不能使用原始值作为键。
  2. 引用关系:

    • Map 中的键和值是强引用关联的,即使没有其他引用指向 Map 中的某个键或值,它们仍然存在于 Map 中。
    • WeakMap 中的键是弱引用关联的,如果没有其他引用指向 WeakMap 中的某个键,该键可能会被垃圾回收机制自动清除。而对于值来说,即使使用 WeakMap,依然是强引用关联的。
  3. 迭代和大小:

    • Map 是可迭代的,可以使用 forEach 方法或 for...of 循环来遍历 Map 中的键值对。
    • WeakMap 不可迭代,没有提供直接的迭代方法或属性,也没有 size 属性来获取存储的键值对的数量。
  4. 键对象特定方法:

    • WeakMap 提供了一些特定于键对象的方法,如 has(key)delete(key),用于检查键是否存在和删除键值对。
    • Map 也提供了类似的方法,不过它还提供了其他的一些方法,如 set(key, value)get(key),用于设置和获取键值对。
  5. 弱引用特性:

    • WeakMap 的键是弱引用关联的,这意味着当键对象不再可访问时(没有其他引用指向键对象),键值对可能被从 WeakMap 中删除。
    • Map 的键是强引用关联的,只有在显式删除键值对或清空整个 Map 时,键值对才会被从 Map 中删除。

WeakMap 的设计目的是为了在不干扰垃圾回收的情况下存储对象关联的附加数据。它通常用于存储一些额外的元数据或私有属性,并且在这些键对象不再被其他代码引用时自动清理相关数据。