JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(一):https://developer.aliyun.com/article/1556700
三. Map映射
3.1. Map的基本使用
另外一个新增的数据结构是Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
- 事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
- 某些情况下我们可能希望通过其他类型作为key,比如对象,当尝试使用一个对象作为另一个对象的键时,JavaScript会将对象转换为字符串。通常情况下,这种转换会导致对象的类型信息和地址被转换为字符串。例如,[object Object]
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))
3.2. Map的常见方法
Map常见的属性:
- size:返回
Map
中元素的个数;
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
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]); }
3.3. WeakMap使用
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key
- 区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象
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常见的方法有四个:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
注意:WeakMap也是不能遍历的
- 因为没有forEach方法,也不支持通过for of的方式进行遍历
// 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属性)。