嘟三~ 嘟三~ 今日份广播题目:“怎么让JavaScript越来越6”。接下来,小菜鸡本人将和大家一起来探讨ES6-ES13的那些酷酷的新特性,从这次广播开始,你也可以炫耀:“这个ES新特性我都用得溜溜的!”
ES6版本邀请了新的舞伴加入:Symbol、Set和Map,这三位舞伴各具特色,各自承担着不同的角色,使得JavaScript这个舞变得更加精彩。
Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
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)
Symbol的独特性使得他成为了一个好帮手。当你需要一个完全不会冲突的属性名时,你可以找到Symbol
const s1 = Symbol("abc")
const s2 = Symbol("cba")
const obj = {}
obj[s1] = "abc"
obj[s2] = "cba"
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])
}
Symbol精灵的独特并不意味着他不能分享他的秘密。当你使用Symbol.for()召唤他的时候,他会首先翻阅他的记事本,看看你是不是之前就已经拜访过他,如果是的话,他就会带着他上次的面孔再次出现。
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
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
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)
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)
}
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
const wset = new WeakSet()
// TypeError: Invalid value used in weak set
wset.add(10)
WeakSet常见的方法:
注意:WeakSet不能遍历
那么这个东西有什么用呢?
比如我们有一个类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"})
另外一个新增的数据结构是Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
const obj = {name: "why"}
const info = {
[obj]: "kobe"
}
console.log(info) // { '[object Object]': 'kobe' }
const obj2 = {}
console.log(info[obj2]) // kobe
如上,const obj = { name: "why" }
创建了一个对象 obj
,然后 const info = { [obj]: "kobe" }
创建了一个新的对象 info
,它的键是 [object Object]
,值是"kobe"
。而 const obj2 = {}
创建了另一个空对象 obj2
,所以 info[obj2]
实际上是 info["[object Object]"]
,所以会打印出 kobe
。
所以这显然不是我们想要的效果,那么我们就可以使用Map:
const obj1 = { name: "why" }
const obj2 = { age: 18 }
const map = new Map()
map.set(obj1, "abc")
map.set(obj2, "cba")
console.log(map.get(obj1))
console.log(map.get(obj2))
我们也可以在创建Map的时候,传入一个数组结构,数组结构中是一个个键值对的数组类型:
const map = new Map([
[obj1, "abc"],
[obj2, "cba"]
])
console.log(map.get(obj1))
console.log(map.get(obj2))
另外Map的key值也是不可以重复的:
const map = new Map([
[obj1, "abc"],
[obj2, "cba"],
[obj1, "nba"]
])
console.log(map.get(obj1))
console.log(map.get(obj2))
Map常见的属性:
Map
中元素的个数;Map常见的方法:
key
、value
,并且返回整个Map对象
key
获取 Map
中的 value
key
,返回 Boolean
类型key
删除一个键值对,返回 Boolean
类型forEach
遍历 Map
const obj = {}
map.set(obj, "mba")
map.get(obj)
map.has(obj)
// map.delete(obj)
// map.clear()
map.forEach((value, key, map) => {
console.log(value, key, map)
})
Map
也可以通过 for...of
进行遍历:
for (const item of map) {
console.log(item)
}
Map
也可以这样来遍历键值对
let myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
// 遍历键
for (let key of myMap.keys()) {
console.log(key);
}
// 遍历值
for (let value of myMap.values()) {
console.log(value);
}
// 遍历键值对
for (let entry of myMap.entries()) {
console.log(entry[0], entry[1]);
}
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
const weakMap = new WeakMap()
// Invalid value used as weak map key
weakMap.set(1, "abc")
// Invalid value used as weak map key
weakMap.set("aaa", "cba")
WeakMap常见的方法有四个:
注意:WeakMap也是不能遍历的
// TypeError: weakMap is not iterable
for (const item of weakMap) {
console.log(item)
}
那么我们的WeakMap有什么作用呢?
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => {
effect();
})
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
// 1.根据对象(target)取出对应的Map对象
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 2.取出具体的dep对象
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
// vue3对raw进行数据劫持
function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
return target[key];
},
set(target, key, newValue) {
const dep = getDep(target, key);
target[key] = newValue;
dep.notify();
}
})
}
上面的代码其实是通过WeakMap来收集响应式对象的依赖:
const obj1 = {
name: "why",
age: 18
}
const obj2 = {
address: "北京市"
}
function nameFn1() {
console.log("nameFn1")
}
function nameFn2() {
console.log("nameFn2")
}
function ageFn1() {
console.log("ageFn1")
}
function addressFn1() {
console.log("addressFn1")
}
const obj1Map = new Map()
obj1Map.set("name", [nameFn1, nameFn2])
obj1Map.set("age", [ageFn1])
weakMap.set(obj1, obj1Map)
const obj2Map = new Map()
obj2Map.set("address", [addressFn1])
weakMap.set(obj2, obj2Map)
迭代器是ES6引入的新特性,它不仅可以帮助我们访问元素集合中的每一个元素,而且还能记住我们访问的位置。就像图书管理员帮你梳理各种草稿,他不仅可以告诉你每张草稿上的内容,而且在你离开之后还能记住你读到哪一页。
咱们试着创建一本书,让迭代器来辅助咱们来阅读
const book = ['P1: Hello, world!', 'P2: Goodbye, world!']
const iterator = book[Symbol.iterator]()
【解释】
[Symbol.iterator]()
是ES6引入的一个特殊的接口(叫做迭代协议)。实现这个接口的对象可以被for...of
循环遍历,也可以作为扩展操作符的对象。 在JavaScript
中,一些内置类型,如Array, String, Map, Set
等默认已经实现了[Symbol.iterator]()
方法。 所以book[Symbol.iterator]()
就是调用book
对象的Symbol.iterator
方法,返回一个迭代器。这个迭代器可以用于遍历book中的每一项。
现在最有意思的部分来了。我们的迭代器有一本神奇的魔法书,每当你翻开这本书的时候,他就会指向下一个章节。
console.log(iterator.next()); // {value: "P1: Hello, world!", done: false}
console.log(iterator.next()); // {value: "P2: Goodbye, world!", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
呼,看来我们已经读完了整本书。每次我们请求 next(),迭代器就会告诉我们新的内容,以及我们是否已经看完整本书(通过done属性)。
一个生成器就是一个特殊的函数,它可以在运行中被暂停和恢复,就像是你可以随时开始或停止冰淇淋机一样。在JavaScript
中,生成器的定义方式是在函数前加一个星号(*)
,并且函数体内可以使用yield
关键词来暂停函数。
假设我们有一个冰淇淋机器(生成器函数):
function* iceCreamGenerator() {
yield 'vanilla';
yield 'chocolate';
yield 'strawberry';
}
现在,我们创建一个冰淇淋生成器(生成器对象)来制作冰淇淋:
const myIceCream = iceCreamGenerator()
每次你想要一个新口味,你只需请求一次:
console.log(myIceCream.next().value); // 'vanilla'
console.log(myIceCream.next().value); // 'chocolate'
console.log(myIceCream.next().value); // 'strawberry'
console.log(myIceCream.next().done); // true,哦豁,冰淇淋没有了
从生成器中得到冰淇淋的过程就像是在点冰淇淋一样,你可以随时停下来(通过yield),并在你准备好的时候再继续。最棒的是,当所有口味都用完时,你会收到一个消息,告诉你冰淇淋已经做完了(通过检查返回的对象的done属性)。
参考代码全部来自 CodeWhy 王元红老师 【来源微信公众号 CodeWhy 】,感谢我的引路人王老师。