前言
嘟三~ 嘟三~ 今日份广播题目:“怎么让JavaScript越来越6”。接下来,小菜鸡本人将和大家一起来探讨ES6-ES13的那些酷酷的新特性,从这次广播开始,你也可以炫耀:“这个ES新特性我都用得溜溜的!”
引子
ES6版本邀请了新的舞伴加入:Symbol、Set和Map,这三位舞伴各具特色,各自承担着不同的角色,使得JavaScript这个舞变得更加精彩。
一、Symbol
1.1. Symbol的基本使用
Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性
- 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
- 比如开发中我们使用混入【Mixin】,那么混入中出现了同名的属性,必然有一个会被覆盖掉
Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol
const s = Symbol() console.log(s) // Symbol()
你无论怎么召唤他,他每次都会以一个全新的面孔出现
const s1 = Symbol() const s2 = Symbol() console.log(s1 === s2) // false
我们也可以在创建Symbol值的时候传入一个描述description【这个是ES2019(ES10)新增的特性】
const s3 = Symbol("abc") console.log(s3.description)
1.2. Symbol作为属性名
Symbol的独特性使得他成为了一个好帮手。当你需要一个完全不会冲突的属性名时,你可以找到Symbol
写法一: 属性名赋值
const s1 = Symbol("abc") const s2 = Symbol("cba") const obj = {} obj[s1] = "abc" obj[s2] = "cba"
写法二: Object.defineProperty
const s1 = Symbol("abc") const s2 = Symbol("cba") const obj = {} Object.defineProperty(obj, s1, { enumerable: true, configurable: true, writable: true, value: "abc" })
写法三: 定义字面量是直接使用
const s1 = Symbol("abc") const s2 = Symbol("cba") const obj = {} const info = { [s1]: "abc", [s2]: "cba" }
之后我们可以通过Symbol值来获取值:
console.log(info[s1]) // abc console.log(info[s2]) // cba // 不能这样获取 console.log(info.s1) // undefined
但是通过Symbol添加的属性名,在遍历时,是无法获取到的:
console.log(Object.keys(info)) console.log(Object.getOwnPropertyNames(info))
如果我们希望获取Symbol的key,那么需要通过 Object.getOwnPropertyNames:
console.log(Object.getOwnPropertySymbols(info)) const symbolKeys = Object.getOwnPropertySymbols(info) for (const key of symbolKeys) { console.log(info[key]) }
1.3. 相同值的Symbol
Symbol精灵的独特并不意味着他不能分享他的秘密。当你使用Symbol.for()召唤他的时候,他会首先翻阅他的记事本,看看你是不是之前就已经拜访过他,如果是的话,他就会带着他上次的面孔再次出现。
- 我们可以使用Symbol.for方法来做到这一点
- 并且我们可以通过Symbol.keyFor方法来获取对应的key
const s1 = Symbol.for("abc") const s2 = Symbol.for("abc") console.log(s1 === s2) // true const key = Symbol.keyFor(s1) console.log(key) // abc const s3 = Symbol.for(key) console.log(s2 === s3) // true
二. Set集合
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
2.1. Set的基本使用
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。你可以将Set想象成一个派对——在这个派对上,每个人都以其独特的个性和风格展现自我,没有人愿意模仿他人或被他人模仿。毕竟,你在派对上穿同样的衣服是多么尴尬的一件事情!
创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
const set1 = new Set() set1.add(10) set1.add(14) set1.add(16) console.log(set1) // Set(3) { 10, 14, 16 } const set2 = new Set([11, 15, 18, 11]) console.log(set2) // Set(3) { 11, 15, 18 }
我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重。
const arr = [10, 20, 10, 44, 78, 44] const set = new Set(arr) const newArray1 = [...set] const newArray2 = Array.from(set) console.log(newArray1, newArray2)
2.2. Set的常见方法
Set常见的属性:
size:返回Set中元素的个数;
Set常用的方法:
add(value):添加某个元素,返回Set对象本身;
delete(value):从set中删除和这个值相等的元素,返回boolean类型;
has(value):判断set中是否存在某个元素,返回boolean类型;
clear():清空set中所有的元素,没有返回值;
forEach(callback, [, thisArg]):通过forEach遍历set;
set.add(100) set.delete(100) set.has(100) set.clear() set.forEach(item => { console.log(item) })
另外Set是支持for of的遍历的:
for (const item of set) { console.log(item) }
2.3. WeakSet使用
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收
const wset = new WeakSet() // TypeError: Invalid value used in weak set wset.add(10)
WeakSet常见的方法:
- add(value):添加某个元素,返回WeakSet对象本身;
- delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
- has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
注意:WeakSet不能遍历
- 因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
- 所以存储到WeakSet中的对象是没办法获取的;
那么这个东西有什么用呢?
- 事实上这个问题并不好回答,我们来使用一个Stack Overflow上的答案;
比如我们有一个类Person,里面有一个running方法,我们只希望你通过Person对象来调用:
class Person { running() { console.log("running", this) } } const p = new Person() p.running() p.running.call({name: "why"})
我们可以通过WeakSet来存储创建出来的对象,并且在调用方法时判断这个对象是否存在于WeakSet中:
const pwset = new WeakSet() class Person { constructor() { pwset.add(this) } running() { if(!pwset.has(this)) throw new Error("不能通过其他对象调用running方法") console.log("running", this) } } const p = new Person() p.running() p.running.call({name: "why"})
.
JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(二):https://developer.aliyun.com/article/1556700