Symbol
特性
- 惟一性
- 属性名属于
Symbol
类型的,都是独一无二的,能够保证不会与其余属性名产生冲突。即便是两个声明彻底同样的也是不相等的。
// 没有参数的状况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的状况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
复制代码
- 不能和其余类型运算
Symbol
值不能与其余类型的值进行运算,会报错。
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string `your symbol is ${sym}` // TypeError: can't convert symbol to string
复制代码
- 类型转换
Symbol
值能够显式转为字符串和布尔值,可是不能转为数值。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
Number(sym) // TypeError
sym + 2 // TypeError
复制代码
Symbol的应用
- 常见的惟一值
Symbol
自己的特性就是任何两个Symbol
类型的值都不相等,因此咱们能够在不知到命名变量时,都设置为Symbol
类型。Symbol
类型不能使用new操做符,由于生成的 Symbol
是一个原始类型的值,不是对象。
- 私有属性
- 因为
Symbol
类型的做为属性名时,遍历对象的时候,该属性不会出如今for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。须要经过Object.getOwnPropertySymbols()
方法,能够获取指定对象的全部 Symbol
属性名。这样就能够把Symbol
类型的属性做为私有属性。
- 新的
API
,Reflect.ownKeys()
方法能够返回全部类型的键名,包括常规键名和 Symbol
键名。
- 消除魔法字符串
- 魔术字符串指的是,在代码之中屡次出现、与代码造成强耦合的某一个具体的字符串或者数值。咱们能够把对应的字符串或数值设置成Symbol类型便可。
const name = {
first: Symbol('detanx')
}
function getName(firstName) {
switch (firstName) {
case name.first: // 魔术字符串
...
}
}
getName(name.first); // 魔术字符串
复制代码
Symbol.for()和Symbol.keyFor()
Symbol.for()
不会每次调用就返回一个新的 Symbol
类型的值,而是会先检查给定的key是否已经存在,若是不存在才会新建一个值。即若是传入相同的key
建立的值是相等的。而Symbol()
每次建立的值都不相同,即便key
相同。
let s1 = Symbol.for('detanx');
let s2 = Symbol.for('detanx');
s1 === s2 // true
let s1 = Symbol('detanx');
let s2 = Symbol('detanx');
s1 === s2 // false
复制代码
Symbol.keyFor()
方法返回一个已登记的 Symbol
类型值的key
。未登记的 Symbol
值,返回undefined
。
let s1 = Symbol.for("detanx");
Symbol.keyFor(s1) // "detanx"
let s2 = Symbol("detanx");
Symbol.keyFor(s2) // undefined
复制代码
Symbol()
写法没有登记机制,因此每次调用都会返回一个不一样的值。
Symbol.for()
为 Symbol
值登记的名字,是全局环境的,无论有没有在全局环境运行。
内置的Symbol值
Set 和 Map 数据结构
Set
- 特性
- 相似于数组,可是成员的值都是惟一的,没有重复的值。
Set
函数能够接受一个数组(或者具备 iterable
接口的其余数据结构)做为参数,用来初始化。
- 内部两个数据是否相同基本和
===
相似,区别是在Set
中NaN
和NaN
也是相等的。
- 应用
[...new Set([1, 1, 2, 3])] // [1, 2, 3]
[...new Set('ababbc')].join('') // "abc"
复制代码
Set
实例的属性和方法
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
复制代码
Set.prototype.add(value):添加某个值,返回 Set 结构自己。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除全部成员,没有返回值。
复制代码
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每一个成员
复制代码
WeakSet
- 与
Set
的区别
WeakSet
的成员只能是对象(null
除外),而不能是其余类型的值。
const ws = new WeakSet();
ws.add(1) // TypeError: Invalid value used in weak set
ws.add(Symbol()) // TypeError: invalid value used in weak set
ws.add(null) // TypeError: invalid value used in weak set
复制代码
WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑WeakSet
对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet
之中。
- 方法。
WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
复制代码
WeakSet
没有size
属性,没有办法遍历它的成员。
Map
- 因为传统的
Object
对象只能使用字符串看成键,因此新增的Map
结构能够将各类类型的值(包括对象)均可以看成键。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
复制代码
- 方法
- 相比
Set
的操做方法,Map
没有add
方法,新增了get
方法和set
方法。遍历方法则是基本是同样的。
Map.prototype.get(key) // 读取key对应的键值,若是找不到key,返回undefined。
Map.prototype.has(key) // 返回一个布尔值,表示某个键是否在当前 Map 对象之中。
复制代码
- 转换
(1) Map
和数组
- 在第二弹中也提到了
Map
和数组之间转换,他们之间互转是很方便的。
// Map转为数组
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
// 数组转为 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
复制代码
(2) Map
和对象
Map
的键都是字符串,它能够无损地转为对象。若是有非字符串的键名,那么这个键名会被转成字符串,再做为对象的键名。对象转为 Map
能够经过Object.entries()
。
// Map => Object
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
// Object => Map
// let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
复制代码
- 应用
- 在存储的数据是键值对的时候,而且键名的类型多是多种类型是可使用
Map
结构。与java
中的Map
结构有区别。
WeakMap
- 与
Map
的区别
WeakMap
只接受对象做为键名(null
除外),不接受其余类型的值做为键名。
const map = new WeakMap();
map.set(1, 2) // TypeError: 1 is not an object!
map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key
map.set(null, 2) // TypeError: Invalid value used as weak map key
map.set(new Number(1), 2) // WeakMap {Number => 2}
复制代码
WeakMap
的键名所指向的对象,不计入垃圾回收机制。一旦再也不须要这两个对象,咱们就必须手动删除这个引用,不然垃圾回收机制就不会释放占用的内存。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
// 不须要 e1 和 e2 的时候
// 必须手动删除引用
arr [0] = null;
arr [1] = null;
复制代码
- WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
- 没有遍历操做(即没有
keys()
、values()
和entries()
方法),也没有size
属性。
- 没法清空,即不支持
clear
方法。WeakMap
只有四个方法可用:get()
、set()
、has()
、delete()
。
- 应用
(1) DOM
节点做为键名
document.getElementById('logo')
是一个DOM
节点,每当发生click
事件,就更新一下状态。咱们将这个状态做为键值放在 WeakMap
里,对应的键名就是这个节点对象。一旦这个 DOM
节点删除,该状态就会自动消失,不存在内存泄漏风险。
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
复制代码
(2) 部署私有属性
- 内部属性是实例的弱引用,因此若是删除实例,它们也就随之消失,不会形成内存泄漏。
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
复制代码