ES6 Set & Map
Set()
这是ECMAScript
的内置构造函数, 无法通过 babel
降级。
传递进去的参数必须具备iterator
接口, 例如[], 'string', arguments, NodeList
. 如何判断一个数据是否具备iterator
接口?
只要原型上有这个东西, 那么它就是具备
iterator
接口的。
Set()
能构造出一种新的数据结构。 没有属性值, 成员的值还是唯一的。
自动去重
字符串:
字符串中重复的值也会被去掉。
Set
上的方法:image.png
add
: 添加元素
delete
: 删除元素
keys
: 返回Set
中的值的集合
clear
: 清空这个Set
forEach
: 遍历方法, 它没有属性名, 所以一个参数就可以遍历了
let s = new Set([1,2,3, {a: 12}, true])
s.forEach(val => {
console.log(val)
})
也可以使用 ES6
的 for of
进行遍历
for(let prop of s){
console.log(prop)
}
Set 与 Array 转换
Array => Set
new Set([1,2,3])
Set => Array
let s = new Set([1,2,3,{}, true])
Array.from(s)
// 或者
let a = [...s]
扩展: Array.from('ahdsidh')
=> ["a", "h", "d", "s", "i", "d", "h"]
, 将字符串转换为数组的便捷方式
一般方法去重
let arr = [1,2,4,5,1,2,3,6,4,5];
let obj = {}
let newArr = []
for (let i = 0; i < arr.length; i++){
if(!obj[ arr[i] ]){
obj[ arr[i] ] = true
newArr.push(arr[i])
}
}
console.log(newArr);
// [ 1, 2, 4, 5, 3, 6 ]
对于数组去重, 挺好用的, 但是一旦里面放入了对象引用:
let o = {}
let arr = [1,2,4,5,1,2,3,6,4,5,o,{name: 'zs'}, o]
// [ 1, 2, 4, 5, 3, 6, {} ]
最后只有一个空对象被保留了下来, 这是因为, 在判断中, obj[ arr[i] ]
如果这个arr[i]
是一个对象类型的话,那么就是obj[ {} ]
, 给obj[{}]
赋值为 true
,然后碰到下一个对象的时候, 又是这样, 之前的就被覆盖了 。 在进行obj[{}] = true
的时候,执行的是这样的: obj[{}.toString()] = true
=> obj[[object Object]] = true
, 就算其中某个对象里面有东西, 不是一个空对象, 执行的也是obj[{name: 'zs'}.toString()] = true
,toString
肯定是原型上的方法, 所以后面每个对象都是这样执行了obj[[object Object]] = true
, 然后最后一个对象将被保留下来。
这就是上面结果的由来。
Set 去重
let arr = [1,2,4,5,1,2,3,6,4,5,o,{name: 'zs'}, o];
console.log([...new Set(arr)]);
// [ 1, 2, 4, 5, 3, 6, {}, { name: 'zs' } ]
Set 实现集合的并、 交、 差集
并集
let arr1 = [1,8,7,5]
let arr2 = [1,2,5,4,6,9]
console.log([...new Set([...arr1, ...arr2])])
// [ 1, 8, 7, 5, 2, 4, 6, 9 ]
交集
let arr1 = [1,8,7,5]
let arr2 = [1,2,5,4,6,9]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
console.log([...s1].filter( ele => s2.has(ele)))
// [ 1, 5 ]
差集
let arr1 = [1,8,7,5]
let arr2 = [1,2,5,4,6,9]
let s1 = new Set(arr1)
let s2 = new Set(arr2)
let a = [...s1].filter( ele => !s2.has(ele))
let b = [...s2].filter( ele => !s1.has(ele))
console.log([...a, ...b]);
// [ 8, 7, 2, 4, 6, 9 ]
Map
Map
也是 ES6
提供的一种新的可以生成一种新的数据结构的构造函数, 内部是键值对的集合。
let m = new Map([['name', 'vey'], ['age', 18], ['sex', 'male']])
console.log(m);
Map { 'name' => 'vey', 'age' => 18, 'sex' => 'male' }
Map
取赋值不能通过对象取赋值的方式获取, 有专门的get/set
方法
m.get()
m.set('name': 'zs')
可以存入一个对象作为键:
m.set({}, '++++')
, 但是取的时候m.get({})
=> undefined
因为它不知道这个{}
指向的是哪里, 对Map
而言, 这个{}
是新的, 即不是原来那个。
所以需要用一个变量来保存这个引用。
m.keys
: 返回所有的键
m.size
: 返回有多少个键值对
m.forEach((val, key, self) => {})
: 遍历方法(值, 键, map本身)
可以使用for of
遍历
for (var ele of m){
console.log(ele);
}
// [ 'name', 'vey' ]
// [ 'age', 18 ]
// [ 'sex', 'male' ]
将键值对以数组的形式放入ele
中, 这就意味这可以用操作数组的方法取操作一个键值对了。
m.entries
: [Map Iterator] { [ 'name', 'vey' ], [ 'age', 18 ], [ 'sex', 'male' ] }
模拟实现 Map
function MyMap() {
this.bucketLength = 8
this.init()
}
MyMap.prototype.init = function () {
this.bucket = new Array(this.bucketLength)
for (var i = 0; i < this.bucket.length; i ++){
this.bucket[i] = {
type: 'bucket_' + i,
next: null
}
}
}
MyMap.prototype.makeHash = function (key) {
// number string boolean null undefined [] {} function 都可以作为键
// 根据不同的键名去计算出一个值, 这个值决定了这个键值对被存放在哪个桶里
var hash = 0
if (typeof key !== 'string'){
if (typeof key === 'number'){
// number NaN(特别考虑 NaN)
// hash = key + '' === 'NaN' ? 0 : key
hash = Object.is(key, NaN) ? 0 : key
} else if (typeof key === 'object') {
// 对象类型存 1 号桶
hash = 1
} else if (typeof key === 'boolean') {
// 布尔值直接类型转换为数字 0 or 1
hash = Number(key)
} else {
// 函数 undefined 存 7 号桶
hash = 7
}
} else {
// string
// a ab abc adsdasdasd 都有可能
// 按照字符串的前三个字符的 AscII 码累加模 8
for (var i = 0; i < 3; i++){
hash += key[i] ? key[i].charCodeAt(0) : 0
}
}
return hash % 8
}
MyMap.prototype.set = function (key, value) {
// 查找之前先算出这个值对应的 hash
var hash = this.makeHash(key)
var temp = this.bucket[hash]
while ( temp.next ){ // 如果桶里有东西
if (temp.next.key === key){ // 如果已经存在, 修改 value
temp.next.value = value
return key + " => " + value
} else { // 如果进入 else ,证明之前没有这个 key,移动指针去遍历下一个节点
temp = temp.next
}
// 如果 while 循环结束都没有找到, 那说明是一个新的键值对
}
// 没有东西肯定不会进入上面的 while 循环
// 循环结束没有
temp.next = {
key: key,
value: value,
next: null
}
return key + " => " + value
}
MyMap.prototype.get = function (key) {
// 取值同样需要知道存在哪里的
var hash = this.makeHash(key)
var temp = this.bucket[hash]
while (temp){
if (temp.key === key){
return temp.value
} else {
temp = temp.next
}
}
return undefined
}
MyMap.prototype.delete = function (key) {
var hash = this.makeHash(key)
var temp = this.bucket[hash]
while (temp.next){
if (temp.next.key === key){
temp.next = temp.next.next
return true
} else {
temp = temp.next
}
}
return false
}
MyMap.prototype.has = function (key) {
var hash = this.makeHash(key)
var temp = this.bucket[hash]
while (temp){
if (temp.next && temp.next.key === key){
return true
} else {
temp = temp.next
}
}
return false
}
MyMap.prototype.clear = function () {
// 清空只需要初始化桶就可以了
this.init()
}