Symbol类型

简介

在传统的JavaScript中,对象的属性名都是由字符串构成的。这样就会带来一个问题,假如一个对象继承了另一个对象的属性,又需要定义新的属性时,很容易造成属性名的冲突。
ES6引入了一种新的基本数据类型Symbol,它表示的是一个独一无二的值。至此JavaScript中就一共存在6种基本数据类型,分别是Undefined类型、Null类型、Boolean类型、String类型、Number类型、Symbol类型。

属性

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable
image.png

特性

1. Symbol值的唯一性

Symbol类型的功能类似于一种唯一标识性的ID,通过Symbol()函数来创建一个Symbol值。

  1. let s = Symbol();
  2. console.log(typeof s); // symbol

在Symbol()函数中可以传递一个字符串参数,表示对Symbol值的描述,主要是方便对不同Symbol值的区分。但是需要注意的是,由于Symbol值的唯一性,任何通过Symbol()函数创建的Symbol值都是不相同的,即使传递了相同的字符串。

  1. const a = Symbol();
  2. const b = Symbol();
  3. const c = Symbol('one');
  4. const d = Symbol('one');
  5. console.log(a === b); // false
  6. console.log(c === d); // false

2. 不能使用new操作符

Symbol函数并不是一个构造函数,因此不能通过new操作符创建Symbol值。

  1. let s1 = new Symbol(); // TypeError: Symbol is not a constructor

3. 不能参与类型运算

Symbol值可以通过toString()函数显示地转换为字符串,但是本身不能参与其他类型值的运算,例如在对Symbol值进行字符串拼接操作时,会抛出异常。

  1. let s4 = Symbol('hello');
  2. s4.toString(); // Symbol(hello)
  3. 's4 content is: ' + s4; // TypeError: Cannot convert a Symbol value to a string

4. 可以使用同一个Symbol值

Symbol.for()函数,它接收一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

  1. let s1 = Symbol.for('one');
  2. let s2 = Symbol.for('one');
  3. s1 === s2; // true

Symbol.for()函数与Symbol()函数这两种写法,都会生成新的Symbol值。它们的区别是,前者会被登记在全局环境中以供搜索,而后者不会。

用法

1. 用作对象属性名

由于每一个Symbol值都是不相等的,它会经常用作对象的属性名,尤其是当一个对象由多个模块组成时,这样能够避免属性名被覆盖的情况。遵循的一个原则就是为对象字面量新增属性时需要使用方括号[],不能通过点运算符为对象添加Symbol属性。因为通过点运算符添加的属性实际是一个字符串,并不是一个Symbol变量。

  1. // 新增一个symbol属性
  2. let PROP_NAME = Symbol();
  3. // 第一种写法
  4. let obj = {};
  5. obj[PROP_NAME] = 'Hello';
  6. // 第二种写法
  7. let obj = {
  8. [PROP_NAME]: 'Hello'
  9. };
  10. // 第三种写法
  11. let obj = {};
  12. Object.deneProperty(obj, PROP_NAME, {
  13. value: 'Hello'
  14. });
  15. obj.PROP_NAME = 'Hello!'; // 这个PROP_NAME实际是一个字符串,并不是一个Symbol变量
  16. console.log(obj[PROP_NAME]); // undefined
  17. console.log(obj['PROP_NAME']); // 'Hello'

2. 用于属性名遍历

使用Symbol作为属性名时,不能通过Object.keys()函数、Object.getOwnPropertyNames()或者for…in来枚举,这样我们可以将一些不需要对外操作和访问的属性通过Symbol来定义。

  1. let obj = {
  2. [Symbol('name')]: 'Hello',
  3. age: 18,
  4. title: 'Engineer'
  5. };
  6. console.log(Object.keys(obj)); // ['age', 'title']
  7. for (let p in obj) {
  8. console.log(p); // 分别会输出:'age' 和 'title'
  9. }
  10. console.log(Object.getOwnPropertyNames(obj)); // ['age', 'title']

因为Symbol属性不会出现在属性遍历的过程中,所以在使用JSON.stringify()函数将对象转换为JSON字符串时,Symbol值也不会出现在结果中。

  1. JSON.stringify(obj); // {"age":18,"title":"Engineer"}

当需要获取Symbol属性时,可以使用专门针对Symbol的API。

  1. // 使用Object的API
  2. Object.getOwnPropertySymbols(obj); // [Symbol(name)]
  3. // 使用新增的反射API
  4. Reect.ownKeys(obj); // [Symbol(name), 'age', 'title']

3. 用于属性区分

  1. // 求图形的面积
  2. function getArea(shape, options) {
  3. let area = 0;
  4. switch (shape) {
  5. case 'triangle':
  6. area = .5 * options.width * options.height;
  7. break;
  8. case 'rectangle':
  9. area = options.width * options.height;
  10. break;
  11. }
  12. return area;
  13. }
  14. console.log(getArea('triangle', { width: 100, height: 100 })); // 5000
  15. console.log(getArea('rectangle', { width: 100, height: 100 })); // 10000
  16. // 字符串'triangle'和'rectangle'会强耦合在代码中。而事实上仅想区分各种不同的形状,并不关心每个形状使用什么字符串表示,只需要知道每个变量的值是独一无二的即可,此时使用Symbol就会很合适。
  17. // 事先声明两个Symbol值,用于作判断
  18. let shapeType = {
  19. triangle: Symbol('triangle'),
  20. rectangle: Symbol('rectangle')
  21. };
  22. function getArea(shape, options) {
  23. let area = 0;
  24. switch (shape) {
  25. case shapeType.triangle:
  26. area = .5 * options.width * options.height;
  27. break;
  28. case shapeType.rectangle:
  29. area = options.width * options.height;
  30. break;
  31. }
  32. return area;
  33. }
  34. console.log(getArea(shapeType.triangle, { width: 100, height: 100 })); // 5000
  35. console.log(getArea(shapeType.rectangle, { width: 100, height: 100 })); // 10000

Set数据结构

简介

ES6中新增了一种数据结构Set,表示的是一组数据的集合,类数组,但是Set的成员值都是唯一的,没有重复。Set本身是一个构造函数,可以接收一个数组或者类数组对象作为参数。

属性

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回实例的成员总数。

    函数

  • Set.prototype.add(value):添加一个值,返回Set结构本身。

  • Set.prototype.delete(value):删除某个值,返回布尔值。
  • Set.prototype.has(value):是否是成员,返回布尔值。
  • Set.prototype.clear():清除所有成员,无返回值。

需要注意的是,向Set实例中添加新的值时,不会发生类型转换。这可以理解为使用add()函数添加新值时,新值与Set实例中原有值是采用严格相等(===)进行比较的,只有在严格相等的比较结果为不相等时,才会将新值添加到Set实例中。

  1. let set = new Set();
  2. set.add(1);
  3. set.add('1');
  4. console.log(set); // Set { 1, '1' }
  5. // 但是NaN是一个特例,NaN与NaN在进行严格相等的比较时是不相等的,但是在Set内部,NaN与NaN是严格相等的,因此一个Set实例中只可以添加一个NaN。
  6. let set = new Set();
  7. set.add(NaN);
  8. set.add(NaN);
  9. console.log(set); // Set { NaN }

用法

(1)单一数组的去重

由于Set成员值具有唯一性,因此可以使用Set来进行数组的去重。

  1. let arr = [1, 3, 4, 2, 3, 2, 5];
  2. console.log(new Set(arr)); // Set { 1, 3, 4, 2, 5 }

(2)多个数组的合并去重

Set可以用于单个数组的去重,也可以用于多个数组的合并去重。实现方法是先使用扩展运算符将多个数组处理成一个数组,然后将合并后得到的数组传递给Set构造函数。

  1. let arr1 = [1, 2, 3, 4];
  2. let arr2 = [2, 3, 4, 5, 6];
  3. let set1 = new Set([...arr1, ...arr2]);
  4. console.log(set1); // Set { 1, 2, 3, 4, 5, 6 }

(3)Set与数组的转换

数组转换为Set:调用Set的构造函数即可;
Set转换为数组:通过Array.from()函数或者扩展运算符即可。

  1. let arr = [1, 3, 5, 7];
  2. // 将数组转换为Set
  3. let set = new Set(arr);
  4. console.log(set); // Set { 1, 3, 5, 7 }
  5. let set = new Set();
  6. set.add('a');
  7. set.add('b');
  8. // 将Set转换为数组,通过Array.from()函数
  9. let arr = Array.from(set);
  10. console.log(arr); // [ 'a', 'b' ]
  11. // 将Set转换为数组,通过扩展运算符
  12. let arr2 = [...set];
  13. console.log(arr2); // [ 'a', 'b' ]

遍历

  • forEach():第一个参数表示的是Set中的每个元素,第二个参数表示的是元素的索引,从0开始。而在Set中没有索引的概念,它实际是键和值相同的集合,第二个参数表示的是键,实际与第一个参数相同,也返回数据值本身。
  • keys():返回键名的遍历器对象Iterator。
  • values():返回键值的遍历器对象Iterator。Set的键值是相等的,所以keys()函数和values()函数实际返回的是相同的值。
  • entries():返回键值对的遍历器对象Iterator。通过for…of循环可以获取每一项的值。
  • 也可以直接用for…of遍历 ```javascript let set = new Set([4, 5, ‘hello’]);

// forEach() set.forEach((item, index) => { console.log(item, index); }); // 4 4 // 5 5 // hello hello

// keys() for (let item of set.keys()) { console.log(item); } // 4 // 5 // hello

// values() for (let item of set.values()) { console.log(item); } // 4 // 5 // hello

// entries() for (let item of set.entries()) { console.log(item); } // [4, 4] // [5, 5] // [‘hello’, ‘hello’]

for (let item of set) { // 不能 let [k,v] of set console.log(item); } // 4 // 5 // hello

  1. <a name="SDF2w"></a>
  2. ## Map数据结构
  3. <a name="E9lOf"></a>
  4. ### 简介
  5. ES6还增加了另一种数据结构Map,**类对象**,它的本质是一种键值对的组合。<br />但是与对象字面量不同的是,**对象字面量的键只能是字符串,对于非字符串类型的值会采用强制类型转换成字符串**,而**Map的键却可以由各种类型的值组成**。
  6. ```javascript
  7. const data = {};
  8. const map = new Map();
  9. const element = document.getElementById('home');
  10. // 传统的对象类型
  11. data[element] = 'first';
  12. console.log(data); // {[object HTMLDivElement]: "first"} 键的值被转换成字符串
  13. // Map
  14. map.set(element, 'first');
  15. console.log(map); // {div#home => "first"} 键的值为DOM元素的真实值,并没有转换为字符串的值。

Map本身是一个构造函数,可以接收一个数组作为参数,数组的每个元素同样是一个子数组,子数组元素表示的是键和值。

  1. const map = new Map([
  2. ['name', 'kingx'],
  3. ['age', 123]
  4. ]);
  5. console.log(map); // Map { 'name' => 'kingx', 'age' => 123 }

属性函数

  • size属性:返回Map结构的成员总数。
  • set(key, value):设置键值,set()函数返回的是当前Map对象,因此set()函数可以采用链式调用的写法。
  • get(key):读取key对应的键值,如果找不到key,返回“undefined”。
  • has(key):表示某个键是否在当前Map对象中,返回一个布尔值
  • delete(key):删除某个键,返回布尔值。
  • clear():清除所有成员,没有返回值。

类似于Set数据结构的元素值唯一性,在Map数据结构中,所有的键都必须具有唯一性。如果对同一个键进行多次赋值,那么后面的值会覆盖前面的值。
如果Map实例的键是基本数据类型,则采用严格相等判断是否为同一个键(NaN除外)。如果Map实例的键是引用数据类型,则需要判断对象是否为同一个引用、是否占据同一个内存地址。

  1. new Map().set(1, 'aaa').set(1, 'bbb');.get(1); // "bbb"
  2. // 对于Number类型数据,+0和-0严格相等
  3. new Map().set(-0, 123).get(+0); // 123
  4. // 字符串'true'与Boolean类型true不严格相等,是两个不同的键。
  5. new Map().set(true, 1).set('true', 2).get(true); // 1
  6. // 对于Undefined类型和Null类型,undefined与null也是两个不同的键。
  7. let map = new Map();
  8. map.set(undened, 3);
  9. map.set(null, 4);
  10. map.get(undened); // 3
  11. map.get(null); // 4
  12. // NaN与NaN不严格相等,但是Map会将其视为一个相同的键。
  13. new Map().set(NaN, 123).set(NaN, 234).get(NaN); // 234
  1. const map = new Map();
  2. // 将数组[0]作为map的键,[0]作为引用类型数据,每次生成一个新的值都会占据新的内存地址,实际为不同的键
  3. map.set([0], '0');
  4. map.set([0], '1');
  5. console.log(map); // Map { [ 0 ] => '0', [ 0 ] => '1' }
  6. let arr = [0];
  7. const map = new Map();
  8. // arr对应的值[0]被两次添加至map中,但是实际指向的是同一个引用,在内存中占据同一个地址,因此后面的值会覆盖前一个值
  9. map.set(arr, '0');
  10. map.set(arr, '1');
  11. console.log(map); // Map { [ 0 ] => '1' }

遍历

与Set一样,Map的遍历同样可以采用4种函数:

  • forEach():第一个参数表示的是值,第二个参数表示的是键
  • keys():返回的是键的集合
  • values():返回的是值的集合
  • entries():返回的键值对的集合,这些集合都是Iterator的实例,可以通过for…of进行遍历。
  • 也可以直接用for…of进行遍历 ```javascript const map = new Map(); map.set(‘name’, ‘kingx’); map.set(‘age’, 12);

map.forEach(function (item, key) { console.log(item, key); }); // kingx name // 12 age

for (let key of map.keys()) { console.log(key); } // name // age

for (let value of map.values()) { console.log(value); } // kingx // 12

for (let obj of map.entries()) { console.log(obj); } // [ ‘name’, ‘kingx’ ] // [ ‘age’, 12 ]

for (let [k,v] of map) { console.log(k,v) } // name kingx // age 12

for (let item of map) { console.log(k,v) } // [ ‘name’, ‘kingx’ ] // [ ‘age’, 12 ]

  1. <a name="IaKK9"></a>
  2. ## weakMap
  3. Map是强引用,weakMap是弱引用,并且key值必须是对象!
  4. ```javascript
  5. let obj = {}
  6. let map = new Map([[obj, 1]])
  7. obj = null // 这里把obj设为null,但是obj并没有被清空,因为它的引用一直在map那里!
  8. console.log(map) // Map(1) {{…} => 1} obj的引用一直存在,没有被垃圾回收!!
  1. let obj = {}
  2. let weakmap = new WeakMap([[obj, 2]])
  3. obj = null
  4. console.log(weakmap) // WeakMap {} 不会马上回收,会在合适的被回收掉

数据结构的转换

Map转换为数组

  1. const map = new Map();
  2. map.set('name', 'kingx');
  3. map.set('age', 12);
  4. // 可以通过扩展运算符实现
  5. const arr = [...map];
  6. console.log(arr); // [ [ 'name', 'kingx' ], [ 'age', 12 ] ]
  7. console.log(arr.flat()); // ['name', 'kingx', 'age', 12]

数组转换为Map

  1. // 可以通过new Map()构造函数实现
  2. const arr = [[ 'name', 'kingx' ], [ 'age', 12 ]];
  3. const map = new Map(arr);
  4. console.log(map); // Map { 'name' => 'kingx', 'age' => 12 }

Map转换为对象

  1. // 如果键是字符串,可以直接转换;不是则会先转换成字符串然后再进行转换。
  2. function mapToObj(map) {
  3. let obj = {};
  4. for(let [key, value] of map) {
  5. obj[key] = value;
  6. }
  7. return obj;
  8. }
  9. console.log(mapToObj(map)); // { name: 'kingx', age: 12 }

或者使用ES10新增的Object.fromEntries方法:

  1. const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
  2. const obj = Object.fromEntries(map);
  3. console.log(obj); // { foo: "bar", baz: 42 }

对象转换为Map

  1. // 只需要遍历对象的属性并通过set()函数添加到Map的实例中即可
  2. function objToMap(obj) {
  3. let map = new Map();
  4. for (let k of Object.keys(obj)) {
  5. map.set(k, obj[k]);
  6. }
  7. return map;
  8. }
  9. console.log(objToMap({yes: true, no: false}));
  10. // Map {"yes" => true, "no" => false}

Map转换为JSON字符串

  1. //第一种是当Map的键名都是字符串时,可以先将Map转换为对象,然后调用JSON.stringify()函数。
  2. function mapToJson(strMap) {
  3. // 先将map转换为对象,然后转换为JSON
  4. return JSON.stringify(mapToObj(strMap));
  5. }
  6. let myMap = new Map().set('yes', true).set('no', false);
  7. console.log(mapToJson(myMap)); // {"yes":true,"no":false}
  8. // 第二种是当Map的键名有非字符串时,可以先将Map转换为数组,然后调用JSON.stringify()函数。
  9. function mapToArrayJson(map) {
  10. // 先通过扩展运算符转换为数组,再转换为JSON
  11. return JSON.stringify([...map]);
  12. }
  13. let myMap2 = new Map().set(true, 7).set({foo: 3}, ['abc']);
  14. mapToArrayJson(myMap2); // [[true,7],[{"foo":3},["abc"]]]

JSON转换为Map

  1. // JSON.parse()==> 对象 ==> Map。
  2. function jsonToMap(jsonStr) {
  3. // 先转换为JSON对象,再转换为Map
  4. return objToMap(JSON.parse(jsonStr));
  5. }
  6. jsonToMap('{"yes": true, "no": false}'); // Map { 'yes' => true, 'no' => false }

Set转换为Map

  1. // Set中以数组形式存在的数据可以直接通过Map的构造函数转换为Map。
  2. const set = new Set([
  3. ['foo', 1],
  4. ['bar', 2]
  5. ]);
  6. console.log(new Map(set)); // Map { 'foo' => 1, 'bar' => 2 }

Map转换为Set

  1. // 遍历Map本身获取到的键和值构成一个数组,然后通过add()函数添加至set实例中。
  2. function mapToSet(map) {
  3. let set = new Set();
  4. for (let [k,v] of map) {
  5. set.add([k, v])
  6. }
  7. return set;
  8. }
  9. const map = new Map().set('yes', true).set('no', false);
  10. mapToSet(map); // Set { [ 'yes', true ], [ 'no', false ] }