ES6学习笔记9 Symbol、Set和Map

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...infor...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']

利用扩展运算符,可以间接地使用数组的mapfilter方法

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转换为对象
主要思路就是新建一个对象,复制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
由于键名是字符串,直接调用 JSON.parse()方法,再转为map。当为JSON数组时,先调用 JSON.parse()方法,再将其传入map构造函数即可
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

WeakMap

WeakMapmap结构类似,区别在于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对象消失,与它绑定的事件函数也会随之消失



猜你喜欢

转载自blog.csdn.net/zjw_python/article/details/80937168