一篇吃透Object、Map、WeekMap和Set
Object
JavaScript中的对象是一种特殊的数据类型,它可以存储多个键值对,并且可以包含其他对象、数组、函数等。以下是关于JavaScript对象的详细解释:
- 创建对象:可以使用字面量语法来创建一个对象,语法如下:
let obj = {
key1: value1,
key2: value2,
//...
}
或者使用构造函数来创建对象:
let obj = new Object();
obj.key1 = value1;
obj.key2 = value2;
- 属性和方法:对象的键值对中的键被称为属性,值被称为属性值。对象的属性值可以是任何数据类型,包括其他对象、数组、函数等。对象的属性也可以是一个方法,即一个函数作为属性值。
- 访问对象属性和方法:可以通过点号(.)或者方括号([])来访问对象的属性和方法。示例:
obj.key1; // 通过点号访问属性
obj['key1']; // 通过方括号访问属性
obj.method(); // 调用对象的方法
- 原型链:JavaScript中的对象是基于原型链的,每个对象都有一个指向一个原型对象的内部链接。当访问一个对象的属性或方法时,如果该对象没有该属性或方法,JavaScript会沿着原型链向上查找,直到找到为止。
- 构造函数和原型:可以使用构造函数和原型来创建对象的模板,所有使用该构造函数创建的对象都会共享原型对象的属性和方法。示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
}
let person1 = new Person('Alice', 20);
let person2 = new Person('Bob', 25);
- JSON对象:JavaScript对象表示法(JSON)是一种轻量级的数据交换格式,它基于JavaScript对象语法,但是不同于JavaScript对象。JSON对象通常用于通过网络传输数据。
Map
map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。
const map = [
["name","张三"],
["age",18],
]
Map数据结构有以下操作方法:
-
size:
map.size
返回Map结构的成员总数。 -
set(key,value) :设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
-
get(key) :该方法读取key对应的键值,如果找不到key,返回undefined。
-
has(key) :该方法返回一个布尔值,表示某个键是否在当前Map对象中。
-
delete(key) :该方法删除某个键,返回true,如果删除失败,返回false。
-
clear() :map.clear()清除所有成员,没有返回值。
Map结构原生提供是三个遍历器生成函数和一个遍历方法
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
- forEach():遍历Map的所有成员。
const map = new Map([
["foo",1],
["bar",2],
])
for(let key of map.keys()){
console.log(key); // foo bar
}
for(let value of map.values()){
console.log(value); // 1 2
}
for(let items of map.entries()){
console.log(items); // ["foo",1] ["bar",2]
}
map.forEach( (value,key,map) => {
console.log(key,value); // foo 1 bar 2
})
存储原理:
map是一种用于存储键值对数据的数据结构。它的原理是使用哈希表来存储键值对。哈希表是一种利用哈希函数来计算索引位置的数据结构,可以实现快速的插入、删除和查找操作。
当向map中添加键值对时,首先会通过哈希函数计算出键的哈希值,然后再将哈希值映射到存储空间的索引位置。这样就可以快速地定位到对应的存储位置,从而实现快速的存取操作。
在map中,不同的键可能会产生相同的哈希值,这就会产生哈希冲突。为了解决哈希冲突,map通常会采用一些策略,如开放寻址法或链表法来处理冲突。
Map和Object对比
Map | Object |
---|---|
Map默认情况不包含任何键,只包含显式插入的键。 | Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。 |
Map的键可以是任意值,包括函数、对象或任意基本类型。 | Object 的键必须是 String 或是Symbol。 |
Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。 | Object 的键是无序的 |
Map 的键值对个数可以轻易地通过size 属性获取 | Object 的键值对个数只能手动计算 |
Map 是 iterable 的,所以可以直接被迭代。 | 迭代Object需要以某种方式获取它的键然后才能迭代。 |
在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
WeakMap
WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
该对象也有以下几种方法:
- set(key,value) :设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
- get(key) :该方法读取key对应的键值,如果找不到key,返回undefined。
- has(key) :该方法返回一个布尔值,表示某个键是否在当前Map对象中。
- delete(key) :该方法删除某个键,返回true,如果删除失败,返回false。
其clear()方法已经被弃用,所以可以通过创建一个空的WeakMap并替换原对象来实现清除。 WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。
而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
设计目的再理解:
在普通的 Map 对象中,如果一个键引用的对象被销毁了,但是该键仍然存在于 Map 对象中,那么这个对象将始终存在于内存中,直到该键被从 Map 对象中删除。这可能导致内存泄漏,因为该对象无法被垃圾回收。
WeakMap 对象解决了这个问题,它中存储的键是“弱引用”,意味着如果键引用的对象被销毁了,那么该键也会被自动从 WeakMap 对象中删除,从而避免了内存泄漏问题。
因此,WeakMap 的设计目的主要是为了提供一种存储键值对的数据结构,同时避免了可能引发内存泄漏的问题,特别适用于存储动态创建的对象,并且这些对象可能会在后续的执行过程中被销毁的情况下。
Set
Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成Set 数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
Set中的特殊值:
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复
- undefined 与 undefined 是恒等的,所以不重复
- NaN 与 NaN 是不恒等的,但是在 Set 中认为NaN与NaN相等,所有只能存在一个,不重复。
- {} {} 两个空对象的指针不一样,所以会重复
Set实例对象的属性:
- size:返回Set实例的成员总数。
Set实例对象的方法:
- add(value):添加某个值,返回 Set 结构本身(可以链式调用)。
- delete(value):删除某个值,删除成功返回true,否则返回false。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
const mySet = new Set(['a', 'a', 'b', 11, 22, 11])
console.log(mySet) // {'a', 'b', 11, 22}
myset.add('c').add({'a': 1})
console.log(mySet) // {'a', 'b', 11, 22, 'c', {a: 1}}
console.log(mySet.size) // 6
mySet.has(22) // true
遍历方法
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回键值对的遍历器。
- forEach():使用回调函数遍历每个成员。
由于Set结构没有键名,只有键值(**或者说键名和键值是同一个值**),所以keys方法和values方法的行为完全一致。
const set = new Set(['a', 'b', 'c'])
for (let item of set.keys()) {
console.log(item)
}
// a
// b
// c
for (let item of set.values()) {
console.log(item)
}
// a
// b
// c
for (let item of set.entries()) {
console.log(item)
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]
// 直接遍历set实例,等同于遍历set实例的values方法
for (let i of set) {
console.log(i)
}
// a
// b
// c
set.forEach((value, key) => console.log(key + ' : ' + value))
// a: a
// b: b
// c: c
Set 对象作用:
- 数组去重(利用扩展运算符)
const mySet = new Set([1, 2, 3, 4, 4])
[...mySet] // [1, 2, 3, 4]
合并两个set对象:
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
let union = new Set([...a, ...b]) // {1, 2, 3, 4}
- 交集
let a = new Set([1, 2, 3])
let b = new Set([2, 3, 6])
let intersect = new Set([...a].filter(x => b.has(x))) // {2, 3} 利用数组的filter方法。
- 差集
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
let difference = new Set([...a].filter(x => !b.has(x))) // {1}
存储原理:
Set存储原理也是基于哈希表的数据结构来实现的。在set中,元素作为键存储在哈希表中,具有唯一性,不允许重复元素存在。
当要向set中插入元素时,先对元素进行哈希函数计算,得到其哈希值,然后根据哈希值确定插入的位置。如果该位置已经有元素存在,就发生了哈希冲突,可以通过开放定址法或链地址法等解决方法来处理。在查找元素时,同样先计算哈希值,然后通过哈希表直接定位元素位置,以此快速找到目标元素。
通过哈希表的实现,set能够实现高效的插入、删除和查找操作,时间复杂度通常为O(1),使得set成为一种高效的存储结构
总结:
- 对象是一种非常灵活的数据类型,它可以存储各种数据,并且可以方便地进行操作和访问。对于熟练掌握对象的使用和原型链的理解对于深入理解JavaScript语言非常重要。
- Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
- WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
- Set是值的集合,值是唯一的可以做数组去重,可以认为只有一个数据,并且set中元素不可以重复且自动排序。
转载自:https://juejin.cn/post/7376936392620720147