写在开头
- 明天就是端午节了,提前祝大家端午安康。
ES6常用但被忽略的方法 系列文章,整理作者认为一些日常开发可能会用到的一些方法、使用技巧和一些应用场景,细节深入请查看相关内容连接,欢迎补充交流。
相关文章
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
`your symbol is ${sym}`
- 类型转换
Symbol 值可以显式转为字符串和布尔值,但是不能转为数值。
let sym = Symbol('My symbol');
String(sym)
sym.toString()
let sym = Symbol();
Boolean(sym)
!sym
Number(sym)
sym + 2
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)
let s2 = Symbol("detanx");
Symbol.keyFor(s2)
Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。
Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。
内置的Symbol值
Set 和 Map 数据结构
Set
- 特性
- 类似于数组,但是成员的值都是唯一的,没有重复的值。
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
- 内部两个数据是否相同基本和
===类似,区别是在Set中NaN和NaN也是相等的。
- 应用
[...new Set([1, 1, 2, 3])]
[...new Set('ababbc')].join('')
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)
ws.add(Symbol())
ws.add(null)
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)
- 方法
- 相比
Set的操作方法,Map没有add方法,新增了get方法和set方法。遍历方法则是基本是一样的。
Map.prototype.get(key)
Map.prototype.has(key)
- 转换
(1) Map和数组
- 在第二弹中也提到了
Map和数组之间转换,他们之间互转是很方便的。
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
new Map([
[true, 7],
[{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)
map.set(Symbol(), 2)
map.set(null, 2)
map.set(new Number(1), 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()