Js 中 ES6+ 数据结构增强

上篇讲了 es6+ 的一些高频增强, 如块级作用域 let / const , 函数默认参数, 剩余参数, 模板字符串, 展开运算符, 解构赋值, Symbl 类型增强等. 都是围绕原来的方案做了一些升级为主, 理解起来也简单, 使用也相对高频, 常用即可.

而本篇则更加侧重 es6 在数据结构上的增强, 主要是 Set 集合, Map 映射等, 这一块虽然内容不多, 但极为重要哈.

Set 集合

在 es6 之前, 我们存储数据的结构主要有两种: 数组, 对象 .

而在 es6 中则新增了两种强大的数据结构: SetMap, 以及他们另外形式的 WeakSetWeakMap.

Set 就和我们数学的 集合 是一样的, 它体现了两个重要的原则: 元素的唯一性 和 无序性. 它是没有字面量创建方式的, 只能通过 Set() 构造函数创建. 觉得最为直观的应用场景就是, 数组元素去重了.

// Set 基本使用

// 创建 Set 结构, 通过构造函数方法
const set = new Set()
set.add(1)
set.add(1) // 重复加没用的
set.add(2)
set.add('cj')

console.log(set) // Set(3) { 1, 2, 'cj' }


// 添加不同对象
set.add({})
set.add({})
console.log(set) // Set(5) { 1, 2, 'cj', {}, {} }

// 添加相同对象
const obj = {}
set.add(obj)
set.add(obj)  // 都是同一个对象地址

console.log(set) // Set(6) { 1, 2, 'cj', {}, {}, {} }

// 数组去重
const arr = [1, 2, 2, 2, 3, 3]

// 原来 
const newArr = []
console.log(newArr.indexOf(11))

for (const item of arr) {
  if (newArr.indexOf(item) == -1) {
    newArr.push(item)
  }
}

console.log(newArr) // [1, 2, 3]

// 现在直接用集合就好了
const arrSet = new Set(arr)
console.log(arrSet) // Set(3) { 1, 2, 3 }

// 将可迭代对象转为数组就好了 
const newArr2 = Array.from(arrSet)
console.log(newArr2) // [1, 2, 3]

// 也可以用展开运算符 
console.log([...arrSet])  // [1, 2, 3]

然后是一些 Set 常用的方法和属性:

  • size 属性: 返回 Set 中元素的个数
  • add(value): 添加某个元素, 返回 Set 对象本身
  • delete(value): 删除等值元素, 返回 boolean 类型
  • has(value) : 判断 Set 中 是否存在某个元素, 返回 boolean 类型
  • clear(): 清空 Set, 没有返回值
  • forEach(callback, ..): 遍历 Set , 也支持 for .. of 遍历
// Set 常用方法

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

// size 属性
console.log(set.size)  // 3

// 添加元素
set.add(4)
console.log(set) // Set(4) { 1, 2, 3, 4 }

// 删除元素
set.delete(4)
set.delete(111) // 删除不存在则不影响
console.log(set) // Set(3) { 1, 2, 3 }

// 是否包含某个元素
console.log(set.has(1)) // true 
console.log(set.has(111)) // false

// 遍历元素
set.forEach(item => {
  console.log(item)
})

for (const item of set) {
  console.log(item)
}

// 清除所有元素
set.clear()
console.log(set) // Set(0) {}

就还是比较简单的, 之前我也是用 js 的数组来完整实现过这个 set 数据结构, 就用的时候有个概念即可.

WeakSet

它是和 Set 类似的一个数据结构, 也是内部元素不能重复. 它和 Set 的区别在于:

  • WeakSet 中只能存放对象类型, 不能存放基本类型
  • WeakSet 对对象是弱引用, 若无其他引用, 引用这个对象, 则会被 GC 回收
// weakSet 使用

const weakSet = new WeakSet()

// 区别1: 不能添加基本类型
// TypeError: Invalid value used in weak set
// weakSet.add(10) 

// 区别2: 对对象是一个弱引用

// 强引用: strong reference

// VE: name不会回收, obj -> name
// let obj = { name: 'youge' } 

// obj -> friends -> name 也不会回收
let obj = {
  name: 'youge',
  friends: {
    name: 'cj'
  }
}

// friends -> name 就断掉了, 里面 name会被回收
obj.friends = null 

// 整个对象指向都断掉了, 里面所有都会被回收
obj = null 

// 弱引用: weak reference
// 依然能访问元素,但不被强保护, 很容易就会被回收

const set = new Set()

set.add(obj) // 强引用
weakSet.add(obj)  // 弱引用

它的常用方法:

  • add(value): 添加元素, 返回 WeakSet 对象自身
  • delete(value): 删除等值元素, 返回 boolean
  • has(value): 判断是否存在某元素, 返回 boolean

注意: WeakSet 是没有 clear() 和 for 遍历的:

  • 它是对对象的弱引用, 遍历取其中元素, 可能造成对象不正常销毁
  • 存到 WeakSet 中的对象不能获取

那它到底能用来干啥?

// weakSet 应用场景

class Person {
  constructor() {
    personSet.add(this)
  }

  running() {
    if (!personSet.has(this)) {
      throw new Error("不能通过非构造方法创建的函数调用 running~");
      
    }

    console.log('running...', this)
  }
}

const personSet = new WeakSet()


const p = new Person()
p.running()

// call 来调用, 能指定 this, 那如何禁止这种很是调用呢
p.running.call({name: 'youge'})


感觉除了写一些框架代码外, 似乎也基本用不到呢.

Map 映射

它是 es6 新增的一个数据结构, 用来存储 映射 关系. 我们之前用的对象有何区别呢, 不也是存储键值对的数据吗?

关键: 对象存储的 key 只能是字符串和 Symbol, 而 Map 支持其他引用类型作为 key.

// Map 基本使用


// 1. js 中不能使用对象作为 key, 只能是字符串
const obj1 = {name: 'youge'}
const obj2 = {name: 'cj'}

const info = {
  obj1: "aaa",  // 这里 obj1 会被当做 "obj1"
  [obj2]: 'bbb' // 计算属性会变为 '[object Object]' 字符串
}

// { obj1: 'aaa', '[object Object]': 'bbb' }
console.log(info[obj1]) 

// 2. Map 允许用其他对象类型作为 key 

const map = new Map()

// 普通类型可以
map.set('aa', "aaa")
map.set(123, "bbb")
map.set(123, "ccc") // 同键名会替换

// 关键是支持对象作为 key 
map.set(obj1, 'obj1')

// Map(3) { 'aa' => 'aaa', 123 => 'ccc', { name: 'youge' } => 'obj1' }
console.log(map)

const map

接着是 Map 常用的属性和方法

  • size 属性: 返回 Map 中的元素个数
  • set(key, value): 添加键值对元素, 并返回 Map
  • get(key): 获取 key 的 value,
  • has(key): 判断是否包含某个 key, 返回 boolean 值
  • delete(key): 根据 key 删除一个键值对, 返回 boolean 值
// Map 常用方法

const obj1 = {name: 'youge'}
const obj2 = {name: 'cj'}

// entries 创建
const map = new Map([[obj1, 'aa'], [obj2, 'bb'], [3, 'cc']])

// Map(3) { { name: 'youge' } => 'aa', { name: 'cj' } => 'bb', 3 => 'cc' }
console.log(map)

// size 属性 
console.log(map.size) // 3

// has(key)
console.log(map.has(obj1)) // true 
console.log(map.has(3)) // true 

// set(k, v)
map.set('d', 'dd')
// Map(4) {
//   { name: 'youge' } => 'aa',
//   { name: 'cj' } => 'bb',
//   3 => 'cc',
//   'd' => 'dd'
// }

map.delete('d')
map.delete('ddd') // 不存在不报错
console.log(map)

console.log(map.get(obj1)) // aa
console.log(map.get('abc')) // undefiend

// 遍历 for 
map.forEach((item, key) => {
  console.log(item, key)
  // aa { name: 'youge' }
  // bb { name: 'cj' }
  // cc 3
})

for (const item of map) {
  console.log(item)
  // { name: 'youge' }, 'aa' ]
  // [ { name: 'cj' }, 'bb' ]
  // [ 3, 'cc' ]
}

// 数组解构
for (const [key, value] of map) {
  console.log(key, value)
  // { name: 'youge' } aa
  // { name: 'cj' } bb
  // 3 cc
}

WeakMap

它是和 Map 类似的一个数据结构, 也是以 key-value 形式的. 它和 Map 的区别在于:

  • WeakMap 中只能存放对象类型, 不能存放基本类型
  • WeakMap 对对象是弱引用, 若无其他引用, 引用这个对象, 则会被 GC 回收

注意: WeakSet 和 WeakMap 的 key 都只能是对象类型

// WeakMap

const obj = { name: "youge" }

// 区别1: WeakMap 的 key 不能是基本类型

// TypeError: Iterator value aa is not an entry object
//const weakMap = new WeakMap(['aa', 'aa'])

// 区别2: WeakMap 是弱引用, Map 是强引用

const map = new Map()
map.set(obj, 'aaa')
// map -> obj -> name 
console.log(map)

const weakMap = new WeakMap()
weakMap.set(obj, 123) 

// weakMap ~ obj 
// WeakMap { <items unknown> }
// js引擎为了实现“弱引用”和内存安全,故意不暴露 WeakMap 具体内容
console.log(weakMap) 

对应的常用方法也有 4个:

  • set(key, value) -> WeakMap
  • get(key) -> value / undefiend
  • has(key) -> boolean
  • delete(key)

因是弱引用, 和 WeakSet 一样, 不支持遍历, 没有 forEach, for ..of 等

// WeakMap 常用方法

const obj = { name: "youge" }

const weakMap = new WeakMap()
weakMap.set(obj, 123) 

console.log(weakMap) //  WeakMap { <items unknown> }

// set(k, v) 方法
const obj2 = { name: 'cj', age: 18 }
weakMap.set(obj2, 'bbb')
console.log(weakMap) // WeakMap { <items unknown> }

// get(k) -> v 方法
console.log(weakMap.get(obj)) // 123
console.log(weakMap.get({})) // undefined

// has(k) 方法
console.log(weakMap.has(obj2)) // true 
console.log(weakMap.has({})) // false 

// delete 方法
console.log(weakMap.delete(obj)) // true 

它的应用场景我感觉会比 WeakSet 要多一些, 比如 Vue3 的响应式原理 就会用到:

// WeakMap 应用:  vue3 响应式原理

const obj1 = {
  name: 'youge',
  age: 18
}

const obj2 = { name: 'cj' }

// 需求: 当改变 obj1 的 name 改变后, 会自动执行一些函数
// 如 obj1NameFn1(),  obj1NameFn2() 等
function obj1NameFn1() {
  console.log('obj1.name 发生改变啦~')
}

function obj2NameFn2() {
  console.log('obj2.name 发生改变啦~')
}

// 监听 obj1.age 变化
function obj1AgeFn1() {
  console.log('obj1.age 发生改变啦~')
}


// 现在当 name 发生变化, obj1NameFn1(), obj2NameFn2() 自动执行
const weakMap = new WeakMap()

const map = new Map()
map.set('name', [obj1NameFn1, obj2NameFn2])

weakMap.set(obj1, map)

let map2 = weakMap.get(obj1) //
// 遍历执行即可 
arrFn = map2.get('name') // 待执行函数列表

// 遍历出来执行函数即可
arrFn.forEach(fn => {
  fn()
})

obj1.name 发生改变啦~
obj2.name 发生改变啦~

这个还是很有用的, 尤其后面我们来自己首先一下 vue3 的响应式原理, 这个结构就很有帮助.

ES6 + 的额外补充

也是有点感悟, 就只要了解到每个知识的原理, 然后才会获得真正的自由.

Array.includes()

它是来自 es7 中的, 用于判断数组是否包含某个元素.

这个场景是非常高频的, 以前我用 python 的时候非常直观, 用 in 操作符:

# 判断某个元素是否在列表中, in
print("apple" in ["aa", "bb", "apple", "cc"]) # True

但当时转用 js 之后就发现有 in 只能通过 indexOf(item) !== -1 的方式, 就没有很丝滑.

// Array.includes() 方法

const names = ['youge', 'cj', 'yaya', 'jack', NaN]

// 判断 cj 是否在 names 中, indexOf 不直观
if (names.indexOf('cj') !== -1) {
  console.log(true)
} else {
  console.log(false)
}

//  es7 添加了 includes() 见名之意
const flag = names.includes('cj')? true : false 
console.log(flag)  // true 

**乘方运算 **

在 es7 之前的乘方需要通过 Math.pow() 完成, 后续则增加了 ** 运算符以用来算乘方

// 乘方运算 **

console.log(Math.pow(2, 4)) // 16

// ** 也可以
console.log(2 ** 4) / 16

很直接, 和 python 一样的而也是用 ** 表示乘方运算.

Object.values

获取对象所有的值, 之前的 Object.keys 是获取对象的所有 key.

// Object.values

const obj = {
  name: 'youge',
  age: 18
}

console.log(Object.keys(obj)) // [ 'name', 'age' ]

console.log(Object.values(obj)) 
// [ 'name', 'age' ]
// [ 'youge', 18 ]

Object.entries()

通过该方法可以获取到一个数组, 存在可枚举属性的 键值对数组

// Object.entries() 转数组

const obj = {
  name: 'youge',
  age: 18
}

// [ [ 'name', 'youge' ], [ 'age', 18 ] ]
console.log(Object.entries(obj))

const objEntries = Object.entries(obj)
objEntries.forEach(([k, v]) => {
  console.log(k, v)
  // name youge
  // age 18
})


// [ [ '0', 1 ], [ '1', 2 ], [ '2', 'c' ] ]
console.log(Object.entries([1, 2, 'c']))

// [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]
console.log(Object.entries('abc'))

字符填充

// 字符填充 padStart() 和 padEnd()

const msg = "hello, world"

const msg2 = msg.padStart(15, "*").padEnd(25, '-')
console.log(msg2) // ***hello, world----------

// 案例, 银行卡只显示后 4为, 前面的的隐藏
const cardNumber = "12345654321789012348599"

const lastFourCard = cardNumber.slice(-4) // 8599
const finalCard = lastFourCard.padStart(cardNumber.length, "*")

console.log(finalCard) // *******************8599

可选链

简化对于判空属性的判断处理 ?. 我感觉非常好用呢.

// 可选链 ?.

const info = {
  name: 'youge',
  friend: {
    girlFriend: {
      name: 'yaya'
    }
  }
}

// 当属性不存在就报错了, 导致后面逻辑都不能运行
// console.log(info.girlFriend.name)

// 以前连续的逻辑判断
if (info && info.friend && info.friend.girlFriend) {
  console.log(info.friend.girlFriend.name) // yaya
}

// es11 的可选链简化表达
console.log(info.friend?.girlFriend?.name)  // yaya 

console.log('其他业务逻辑')

逻辑赋值

// 逻辑赋值

// 1. ||=

let msg = undefined
// msg = msg || "default"
msg ||= "default"
console.log(msg) // default

// 2. &&=

let info = { name: 'youge' }

// info = info && info.name 
info &&= info.name

console.log(info)  // youge

// 3. ??=  
let res = 0
// res ||= "res"
res ??= "res"
console.log(res) // 0

**for ... in ** 遍历对象的 key , 包含原型上可枚举的, 只是自己的话就是 obj.hasOwnProperty(key) -> boolean

// for-in 遍历对象属性(含原型)

const obj = { name: 'youge', age: 18 }

for (const key in obj) {
  console.log(key)
  // name , age
}

// 给原型上加一个属性 height
Object.prototype.height = 1.8

for (const key in obj) {
  console.log(key)
  // name, age, height
}

// 稳妥办法还是 hawOwnProperty() 只看自己的
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key) // name, age
  }
}

注意别和 for .. of 搞混了, 它是用来遍历可迭代对象的, 比如 Array, String, Map, Set, NodeList 等实现了 [Symbol.iterator] 方法的对象.

特性 说明
🎯 遍历的是 值(value) 不是键(key)
🚫 不会遍历原型链 只访问对象自身的可迭代数据
🚫 不能直接遍历普通对象 普通对象不是可迭代的(没有 Symbol.iterator
✅ 支持 break, continue, return 可控制循环流程
  • inkey in object
  • ofvalue of iterable

至此, 关于 es6+ 的一些常用补充内容就到这了, 本篇主要是重点要掌握 Set, Map / WeakMap 的使用, 然后关于 es6 的其他重点知识如 Promise, Async..await, Iterator, Reflect, Esmodule 等重要内容会继续以更多篇幅来展开学一下的.

posted @ 2025-08-21 21:59  致于数据科学家的小陈  阅读(8)  评论(0)    收藏  举报