Set()

这是ECMAScript的内置构造函数, 无法通过 babel 降级。
传递进去的参数必须具备iterator接口, 例如[], 'string', arguments, NodeList. 如何判断一个数据是否具备iterator接口?


只要原型上有这个东西, 那么它就是具备 iterator接口的。

Set()能构造出一种新的数据结构。 没有属性值, 成员的值还是唯一的。
自动去重

image.png

字符串:

image.png

字符串中重复的值也会被去掉。
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)
})

也可以使用 ES6for 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()] = truetoString肯定是原型上的方法, 所以后面每个对象都是这样执行了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()
}