第十四节:ES6之Symbol、Set和WeakSet、Map和WeakMap详解
一. Symbol详解
1. 说明
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是 JavaScript 语言的第七种数据类型, 前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
PS: ES10中,Symbol还新增了一个description描述符
{ const s1 = Symbol(); const s2 = Symbol(); const s3 = Symbol("ypf"); const s4 = Symbol("ypf"); console.log(s1 === s2); //false console.log(s3 === s4); //false console.log(s3.description); //ypf }
2. Symbol作为key
(1). 定义key
(2). 新增属性
(3). Object.defineProperty方式定义
(4). 使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值
{ const s1 = Symbol(); const s2 = Symbol(); const s3 = Symbol(); const s4 = Symbol(); // 定义属性 let obj = { [s1]: "ypf1", [s2]: "ypf2", }; // 新增属性 obj[s3] = "ypf3"; // defineProperty的方式定义属性 Object.defineProperty(obj, s4, { enumerable: true, configurable: true, writable: true, value: "ypf4", }); //获取属性 console.log(obj[s1], obj[s2], obj[s3], obj[s4]); //ypf1 ypf2 ypf3 ypf4 console.log(Object.keys(obj)); //获取不到 [] console.log(Object.getOwnPropertyNames(obj)); //获取不到 [] console.log(Object.getOwnPropertySymbols(obj)); //能获取 //[ Symbol(), Symbol(), Symbol(), Symbol() ] const sKeys = Object.getOwnPropertySymbols(obj); // for (const sKey of sKeys) { console.log(obj[sKey]); //依次输出 ypf1 ypf2 ypf3 ypf4 } }
3. Symbol.for()/Symbol.keyFor()
(1). Symbol.for(): 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
注:Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
(2). Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
注:只能获取Symbol.for("foo");创建的key,不能获取symbol获取的key
{ const s1 = Symbol.for("ypf"); const s2 = Symbol.for("ypf"); console.log(s1 === s2); //true } { const s1 = Symbol("foo"); console.log(Symbol.keyFor(s1)); // undefined const s2 = Symbol.for("foo"); console.log(Symbol.keyFor(s2)); // foo }
4. 应用场景
消除魔术字符串:魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
代码分享:
{ //原始写法:字符串Triangle和Circle就是魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。 function getArea(shape, options) { let area = 0; switch (shape) { case "Triangle": // 魔术字符串 area = 0.5 * 0.5; break; /* ... more code ... */ } return area; } getArea("Triangle", { width: 100, height: 100 }); // 魔术字符串 } { //常用的消除魔术字符串的方法,就是把它写成一个变量。 const shapeType = { triangle: "Triangle", }; function getArea(shape, options) { let area = 0; switch (shape) { case shapeType.triangle: area = 0.5 * 0.5; break; } return area; } getArea(shapeType.triangle, { width: 100, height: 100 }); } { // 可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用 Symbol 值。 const shapeType = { triangle: Symbol(), circle: Symbol(), }; function getArea(shape) { let area = 0; switch (shape) { case shapeType.triangle: area = 0.5 * 0.5; break; case shapeType.circle: // ... more code ... break; } return area; } console.log(getArea(shapeType.triangle)); }
二. Set详解
1. 含义
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
2. 基本用法
(1). 通过new来实例化set结构
(2). size:返回Set中元素的个数;
(3). add(value):添加某个元素,返回Set对象本身;
(4). delete(value):从set中删除和这个值相等的元素,返回boolean类型;
(5). has(value):判断set中是否存在某个元素,返回boolean类型;
(6). clear():清空set中所有的元素,没有返回值;
(7). 遍历:
forEach():使用回调函数遍历每个成员
for...of:可以直接遍历每个成员
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
代码分享:
{ //生成Set实例 const myS1 = new Set(); let myS2 = new Set([1, 2, 3, 4]); // 添加数据 myS1.add(1); myS1.add(2); myS1.add(3); myS1.add(4); myS1.add(5).add(2); // size属性 console.log(myS1.size); //5 // delete方法 myS1.delete(3); //has方法 console.log(myS1.has(3)); //false //遍历 { //写法1 myS1.forEach(item => console.log(item)); //1 2 4 5 //写法2 for (const item of myS1) { console.log(item); //1 2 4 5 } console.log(myS1.keys()); // [Set Iterator] { 1, 2, 4, 5 } console.log(myS1.values()); // [Set Iterator] { 1, 2, 4, 5 } console.log(myS1.entries()); //[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 4, 4 ], [ 5, 5 ] } } }
3. 用途
(1). 数组去重: 先将数组放到new Set中,然后通过展开运算符或者Array.from将set转换成普通数组
(2). 合并去重:将多个数组通过展开运算符放到Set实例中,然后通过展开运算符或者Array.from将set转换成普通数组
(3). 交集
(4). 差集
代码分享:
// 2.1 数组去重 { const myArray = [10, 20, 30, 30, 20, 50, 40]; // 写法1 let newArray = []; for (const item of myArray) { if (!newArray.includes(item)) { newArray.push(item); } } console.log(newArray); //[ 10, 20, 30, 50, 40 ] // 写法2 const mySet1 = new Set(myArray); let newArray2 = [...mySet1]; console.log(newArray2); //[ 10, 20, 30, 50, 40 ] // 写法3 const mySet3 = new Set(myArray); let newArray3 = Array.from(mySet3); console.log(newArray3); //[ 10, 20, 30, 50, 40 ] } // 2.2 合并去重 { const array1 = [1, 3, 4, 5]; const array2 = [2, 3, 5, 6]; let mySet = new Set([...array1, ...array2]); let newArray = [...mySet]; console.log(newArray); //[ 1, 3, 4, 5, 2, 6 ] } // 2.3 交集 { const array1 = [1, 3, 4, 5]; const array2 = [2, 3, 5, 6]; let result = new Set(array1.filter(item => array2.includes(item))); console.log([...result]); } // 2.3 差集 { let arr1 = [1, 2, 3, 4]; let arr2 = [2, 3, 4, 5, 6]; let s1 = new Set([1, 2, 3, 4]); let s2 = new Set([2, 3, 4, 5, 6]); let arr3 = new Set(arr1.filter(item => !s2.has(item))); let arr4 = new Set(arr2.filter(item => !s1.has(item))); console.log(arr3); // Set(1) { 1 } console.log(arr4); // Set(2) { 5, 6 } console.log([...arr3, ...arr4]); // [ 1, 5, 6 ] }
三. WeakSet详解
1. 含义
另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
{ const myWeakSet = new WeakSet(); let obj = { name: "ypf" }; myWeakSet.add(obj); // 下面报错 // myWeakSet.add(10); //TypeError: Invalid value used in weak set }
2. 基本用法
(1).add(value):添加某个元素,返回WeakSet对象本身;
(2).delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
(3).has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
3. 使用场景
WeakSet不能遍历
因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
所以存储到WeakSet中的对象是没办法获取的;
{ const personSet = new WeakSet(); class Person { constructor() { personSet.add(this); } running() { if (!personSet.has(this)) { throw new Error("不能通过非构造方法创建出来的对象调用running方法"); } console.log("running~", this); } } let p = new Person(); p.running(); p = null; p.running.call({ name: "ypf" }); }
四. Map详解
1. 含义
ES6新增了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
{ const obj1 = { name: "ypf1" }; const obj2 = { name: "ypf2" }; const myMap1 = new Map(); myMap1.set(obj1, "aaa"); myMap1.set(obj2, "bbb"); myMap1.set("003", "ccc"); console.log(myMap1); // 实例化的时候直接赋值 const myMap2 = new Map([ [obj1, "aaa"], [obj2, "bbb"], ["003", "ccc"], ]); console.log(myMap2); }
2. 常用方法
size:返回Map中元素的个数;
set(key, value):在Map中添加key、value,并且返回整个Map对象;
get(key):根据key获取Map中的value;
has(key):判断是否包括某一个key,返回Boolean类型;
delete(key):根据key删除一个键值对,返回Boolean类型;
clear():清空所有的元素;
forEach(callback, [, thisArg]):通过forEach遍历Map;
Map也可以通过for of进行遍历。
代码分享:
{ const obj1 = { name: "ypf1" }; const obj2 = { name: "ypf2" }; const myMap = new Map([ [obj1, "aaa"], [obj2, "bbb"], ["003", "ccc"], ]); myMap.set("001", "ypf1"); console.log(myMap.size); //4 console.log(myMap.get("001")); //ypf1 console.log(myMap.has("001")); //true myMap.delete(obj1); // 遍历 console.log("-------------遍历--------------"); myMap.forEach((item, key) => { console.log(key, item); }); for (const item of myMap) { console.log(item[0], item[1]); } for (const [key, value] of myMap) { console.log(key, value); } }
五. WeakMap详解
1. 含义
和Map类型相似的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
2. 常用方法
set(key, value):在Map中添加key、value,并且返回整个Map对象;
get(key):根据key获取Map中的value;
has(key):判断是否包括某一个key,返回Boolean类型;
delete(key):根据key删除一个键值对,返回Boolean类型;
{ const obj = { name: "obj1" }; //区别一: 不能使用基本数据类型 // weakMap.set(1, "ccc") // WeakMap和Map的区别二: const map = new Map(); map.set(obj, "aaa"); const weakMap = new WeakMap(); weakMap.set(obj, "aaa"); // 3.常见方法 // get方法 console.log(weakMap.get(obj)); // has方法 console.log(weakMap.has(obj)); // delete方法 console.log(weakMap.delete(obj)); // WeakMap { <items unknown> } console.log(weakMap); }
3. 响应式原理中WeakMap的使用
代码分享:
{ // 应用场景(vue3响应式原理) const obj1 = { name: "why", age: 18, }; function obj1NameFn1() { console.log("obj1NameFn1被执行"); } function obj1NameFn2() { console.log("obj1NameFn2被执行"); } function obj1AgeFn1() { console.log("obj1AgeFn1"); } function obj1AgeFn2() { console.log("obj1AgeFn2"); } const obj2 = { name: "kobe", height: 1.88, address: "广州市", }; function obj2NameFn1() { console.log("obj1NameFn1被执行"); } function obj2NameFn2() { console.log("obj1NameFn2被执行"); } // 1.创建WeakMap const weakMap = new WeakMap(); // 2.收集依赖结构 // 2.1.对obj1收集的数据结构 const obj1Map = new Map(); obj1Map.set("name", [obj1NameFn1, obj1NameFn2]); obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2]); weakMap.set(obj1, obj1Map); // 2.2.对obj2收集的数据结构 const obj2Map = new Map(); obj2Map.set("name", [obj2NameFn1, obj2NameFn2]); weakMap.set(obj2, obj2Map); // 3.如果obj1.name发生了改变 // Proxy/Object.defineProperty obj1.name = "james"; const targetMap = weakMap.get(obj1); const fns = targetMap.get("name"); fns.forEach(item => item()); }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。