Js 中 ES6+ 数据结构增强
上篇讲了 es6+ 的一些高频增强, 如块级作用域 let / const , 函数默认参数, 剩余参数, 模板字符串, 展开运算符, 解构赋值, Symbl 类型增强等. 都是围绕原来的方案做了一些升级为主, 理解起来也简单, 使用也相对高频, 常用即可.
而本篇则更加侧重 es6 在数据结构上的增强, 主要是 Set 集合, Map 映射等, 这一块虽然内容不多, 但极为重要哈.
Set 集合
在 es6 之前, 我们存储数据的结构主要有两种: 数组, 对象 .
而在 es6 中则新增了两种强大的数据结构: Set 和 Map, 以及他们另外形式的 WeakSet 和 WeakMap.
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 |
可控制循环流程 |
in→ key in objectof→ value of iterable
至此, 关于 es6+ 的一些常用补充内容就到这了, 本篇主要是重点要掌握 Set, Map / WeakMap 的使用, 然后关于 es6 的其他重点知识如 Promise, Async..await, Iterator, Reflect, Esmodule 等重要内容会继续以更多篇幅来展开学一下的.

浙公网安备 33010602011771号