不常见的数据类型
这里是一些不常见的数据类型
Symbol
可以创建一个唯一的值
new Symbol();
// Uncaught TypeError: Symbol is not a constructor
const symbol1 = Symbol("lucas");
const symbol2 = Symbol("lucas");
symbol1 !== symbol2; // true
js 运行过程会设置一个全局 Symbol 注册表,可以通过Symbol.for(key)检索指定 key 的 Symbol 值
使用场景
- 定义常量(枚举)
- 对象私有属性。需要注意的是:遍历对象的时候,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,可以用Object.getOwnPropertySymbols()获取 - 实现一个可遍历的对象
const obj = {
from: 0,
to: 10,
[Symbol.iterator]: function () {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};
},
};
for (let num of obj) {
console.log(num);
}
- 其他内置 Symbol
Symbol.matchSymbol.toPrimitive
BigInt
可以用任意精度表示整数,并且可以正确执行整数运算而不会溢出。同为 BigInt 类型的变量才能做运算,并且 BigInt 永远不会等于 Number
new BigInt(5);
// Uncaught TypeError: BigInt is not a constructor
5n === BigInt(5);
const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// 9007199254740991n
const maxPlusOne = previousMaxSafe + 1n;
// 9007199254740992n
BigInt(1.5);
// RangeError
BigInt("1.5");
// SyntaxError
// 注意这里的类型转换
typeof 1n === "bigint"; // true
typeof BigInt("1") === "bigint"; // true
但要注意运算符有使用局限,比如不能用单目运算符、不能使用无符号右移等,详见 BigInt-MDN
Map
Map 跟普通对象的区别是不限键类型,并且是有序的
new Map()map.set(key, value)map.get(key)map.has(key)map.delete(key)map.clear()map.size
const map = new Map();
map.set("1", "lucas");
map.set(1, "gogogo");
console.log(map.get(1)); // 'gogogo'
console.log(map.get("1")); // 'lucas'
console.log(map.size); // 2
const obj = { name: "lucas" };
map.set(obj, "gogogo");
将对象转换成 Map 对象
const prices = new Map([
["banana", 1],
["orange", 2],
["meat", 10],
]);
使用 Object.fromEntries 将 Map 对象转换成一个普通对象
const prices = Object.fromEntries([
["banana", 1],
["orange", 2],
["meat", 10],
]);
// prices: { banana: 1, orange: 2, meat: 10 }
遍历 Map 对象可以用 map.keys()/map.values()/map.entries(),返回迭代器对象,每个迭代器对象都有 next 方法,每次调用 next 方法返回一个键值对或值或键值对
const map = new Map([
["banana", 1],
["orange", 2],
["meat", 10],
]);
for (let [key, value] of map) {
console.log(key, value);
}
Set
Set 跟普通数组的区别主要在于唯一,并且没有下标的概念
new Set(iterable)set.add(value)。返回 Set 对象本身set.delete(value)。删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 falseset.has(value)set.clear()set.size
let set = new Set();
let a = { name: "a" };
let b = { name: "b" };
let c = { name: "c" };
set.add(a);
set.add(b);
set.add(c);
set.add(a);
set.add(b);
alert(set.size); // 3
可以使用 for..of 或 forEach 来遍历 Set。除此之外,也可以用 set.keys()/set.values()/set.entrie() 实现遍历
let set = new Set(["oranges", "apples", "bananas"]);
set.forEach((value, valueAgain, set) => {
// 这里的三个参数,前两者是一样的。注意下标是数组特有的东西
console.log(value, valueAgain);
});
WeakMap
WeakMap 只能接受对象或 Symbol(ES2023 起)作为键,并保持了对键名所引用的对象的弱引用。当没有其他外部变量引用该对象时,垃圾回收机制(GC)会自动释放该对象占用的内存,同时它在 WeakMap 中对应的键值对也会自动消失。
注意:正因为其动态回收的特性,WeakMap 不支持遍历(没有 keys()、values() 等方法),也没有 size 属性。
开源库实践 / 面试高频:
- Vue 3 响应式底层(reactive):Vue3 使用
WeakMap来建立代理对象(Target)与它的依赖订阅者集合(DepsMap)的映射关系。当一个组件被卸载,或者某个深层对象不再被业务代码引用时,GC 会自动回收它,完美避免了内存泄漏。- 深拷贝防止循环引用:在手写
cloneDeep时,通常会传入一个new WeakMap()作为 Hash 表来记录已经拷贝过的对象。一旦函数执行完毕,这个 WeakMap 就会被销毁,不会造成额外的内存占用。
WeakSet
WeakSet 只能接受对象作为值,并且储存的对象都是被弱引用的。如果没有其他的变量引用这个对象,则该对象会被垃圾回收掉(不考虑该对象是否还存在于 WeakSet 中)。
开源库实践: DOM 节点追踪 / 状态标记:如果你想追踪一组 DOM 节点是否已经被处理过(比如添加了特定的事件监听),你可以把它们放入一个
WeakSet中。当这些 DOM 节点从页面上被移除(如通过removeChild)并且不再被引用时,它们会自动从内存中消失,无需你手动去清理这个 Set。