Symbol
ES6引入一种新的数据类型Symbol,表示独一无二的值。Symbol值通过Symol函数生成,可传入一个字符串参数,表示对Symbol实例的描述(即使两个Symbol描述相同,值也不同),用于区分不同的Symbol值。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
Symbol函数不能使用new命令,如果Symbol函数的参数是一个对象,则会调用该对象的toString方法,将其转换为字符串,再生成一个Symbol值
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
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
symbol可以作为对象属性名,这样就保证不会与其他属性名产生冲突。Symbol作为对象属性名时不能使用点运算符,其必须放在方括号中。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()、
JSON.stringify()返回,但可以通过Object.getOwnPropertySymbols()方法,来获取指定对象的所有Symbol属性名。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
如果需要使用同一个Symbol值,可以通过Symbol.for方法。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.for()与
Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,而后者不会。
Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。若Symbol不是通过Symbol.for()创建的,则返回undefined。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
Symbol.for创建 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。
Set
ES6提供了set数据结构,其类似于数组,但成员的值都是唯一的,没有重复,有点像Python的元组。set函数可以接受一个数组(或具有iterable接口的数据结构),用来初始化,其自动去除重复的值。
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
在set结构中,NaN等于自身,且两个对象总是不等的
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
set实例的属性:
- Set.prototype.constructor : 构造函数
- Set.prototype.size : 返回set实例的成员总数
set实例的方法:
- add(value) : 添加某个值,返回Set结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示参数是否为set成员
- clear():清除所有成员,没有返回值
- keys():返回键名的遍历器,由于set结果没有键名,故返回键值
- values():返回键值的遍历器
- entries():返回键值对的遍历器,由于set结果没有键名,故返回两个相同键值组成的数组
- forEach():使用回调函数遍历每个成员,没有返回值
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
也可以省略values方法,直接用for...of循环遍历,或者使用扩展运算符
for (let x of set) {
console.log(x);
}
// red
// green
// blue
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
利用扩展运算符,可以间接地使用数组的map和
filter方法
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
通过filter方法可以实现并集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
如果想在遍历过程中改变原来Set的结构,有两种方法。第一种是利用原Set结构映射出一个新的Set,然后覆盖原有的值。另外一种方法就是利用Array.from方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
第一个区别是WeakSet 的成员只能是对象,而不能是其他类型的值。第二个区别是WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。因此WeakSet不可遍历,且不具有size属性。
使用WeakSet构造函数创建实例,接受一个数组或类似组对象(具有iterable接口)作为参数,该数组的所有成员都会成为WeakSet实例的成员。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
方法
- WeakSet.prototype.add(value) : 添加一个成员
- WeakSet.prototype.delete(value) : 删除一个指定成员
- WeakSet.prototype.has(value) : 返回一个布尔值,表示某个值是否存在于实例中
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
Map
ES6引入Map数据结构,其与JS的对象类似,区别在于其"键"的范围不限于字符串,各种类型的值都可以当做键。而对象则是字符串-值结构。
使用Map构造函数创建实例,其可以接受一个键值对数组(任何具有iterable接口,成员为双元素数组的数据结构都可以,包括Set和Map)作为初始化参数
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
属性
- size : 返回Map结构的成员总数
方法
- set(key, value):设置键名key对应的键值为value,返回Map,若键名已存在,则对应的键值将被更新
- get(key) : 返回key的键值,找不到,返回undefined
- has(key) : 查找对应的键,存在返回true,否则为false
- delete(key) : 删除某个键,成功返回true,否则返回false
- clear():清除所有成员
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():遍历Map的所有成员
const m = new Map();
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map转换为其他数据结构
- Map转化为数组 map转换为数组的最便捷方法就是使用扩展运算符(...)
const map = new Map( [ [a,1],[b,2] ] );
[...map]
//[ [a,1],[b,2] ]
- 数组转换为Map 将数组传入Map函数就可以,转换为Map
- Map转换为对象
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
- 对象转换为Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
- Map转换为JSON
分为两种情况,当map键名全是字符串时,先转为对象,再使用JSON.stringify()方法。若键值有非字符串,则将其转为数组后,再使用JSON.stringify()方法。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
- JSON转换为Map
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
WeakMap
WeakMap与map结构类似,区别在于WeakMap只接受对象(null除外)作为键名,且其键名不计入垃圾回收机制(键值正常),因此它没有size属性,也不支持各种遍历方法,同时也没有clear方法,只有四个方法可用:set(),get(),has(),delete()
WeakMap 应用的典型场合就是 DOM 节点作为键名
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
上述代码中,每当myElement这个DOM节点被点击时,timeClicked就被更新一次,当这个DOM节点被删除时,该状态自动消失,不存在内存泄漏风险
除此之外,注册监听事件的对象也很可以用WeakMap实现
const listener = new WeakMap();
listener.set(element1, handler1);
listener.set(element2, handler2);
element1.addEventListener('click', listener.get(element1),false);
element2.addEventListener('click', listener.get(element2),false);
上述代码,一旦DOM对象消失,与它绑定的事件函数也会随之消失