开发人员已经尝试在ES6之前实现映射,但是由于Javascript中处理对象属性的方式,出现了一些问题。在一个对象中,每个属性都必须是一个字符串。因此,如果给对象一个具有不同类型的键,它将被强制转换为字符串。

let map = {}

map[5] = 4
map[{}] = 'An object'

// { '5': 4, '[object Object]': 'An object' }

如你所见,我们的5变为'5',我们的空对象变为''object Object]'。那是一些严重的限制!

在ES6中,Maps使用Object.is()方法来比较键,就像Sets使用它们的值一样。地图也不会将每个键都设为字符串,每种类型都是允许的。

Object.is(5, '5') // false
Object.is({}, {}) // false

构造函数

那么,如何创建一个新的Map?通过使用new Map()。您还可以使用数组数组初始化地图:

const map = new Map()
// Map {}

const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])
// Map { 5 => 42, 'name' => 'Paul', 'age' => 45 }

在数组数组中,每个数组代表一个键值对。每个数组中的第一项将成为键,第二项将成为值。结构可能看起来很奇怪,但这是确保我们可以允许任何类型的数据的最佳方式。

地图方法

要与地图互动,您可以使用几种方法。

  • set(key, value)方法将一对添加到地图中。
  • get(key)方法从地图中检索值。如果未找到任何内容,该get方法将返回undefined
  • has(key)方法检查映射中是否存在密钥。返回true或false。
  • delete(key)方法从地图中删除键及其值。
  • clear()方法从地图中删除所有键和值。
  • 最后,maps具有一个size属性,该属性返回地图中键/值对的数量。
const map = new Map()

map.set(5, "Hello")
map.set("5", "World")
map.set("John", "The revelator")
map.size // 3
// Map { 5 => 'Hello', '5' => 'World', 'John' => 'The revelator' }

map.get(5) // Hello
map.has('5') // true
map.get('Random') // undefined
map.has('John') // true

map.delete('5')
map.size // 2
// Map { 5 => 'Hello', 'John' => 'The revelator' }

map.clear()
map.size // 0
// Map {}

对象键在地图中

正如我之前提到的,对象可以用作地图中的键。

const map = new Map()
let obj1 = {}
let obj2 = {}

map.set(obj1, 12)
map.set(obj2, "OBJECT")
map.size // 2
// Map { {} => 12, {} => 'OBJECT' }

如您所见,即使我们使用两个空对象作为键,我们也在使用地图中这些对象的引用。因此,用于比较键的Object.is()返回false。再次注意,对象不会被强制转换为字符串。

迭代

您可以使用forEach()迭代Map。传递的回调接收三个参数:我们正在使用的值,键和映射。

const map = new Map([[5, 42], ["name", "Paul"], ["age", 45]])

map.forEach((value, key, thisMap) => {
    console.log(`${key} => ${value}`)
    console.log(thisMap === map)
})

//5 => 42
//true

//name => Paul
//true

//age => 45
//true

弱地图

弱地图遵循弱集的相同原则。在弱地图中,每个必须是一个对象。弱映射用于存储弱对象引用。那是什么意思?

const map = new Map()
let obj1 = {}
map.set(obj1, 12)
//Map { {} => 12 }
obj1 = null // I remove the obj1 reference
// Map { {} => 12 } // But the reference still exists in the map anyway

在这种情况下,我们的对象引用仍然存在于地图中。删除其他地方的引用不会将其从地图中删除。它不是垃圾收集来释放内存。在某些情况下,您可能希望优化内存使用并避免内存泄漏。这就是WeakMap为您所做的。如果对象的引用在程序中的其他位置消失,它也将从WeakSet中删除。

const map = new WeakMap()

let obj = {} // creates a reference to obj
map.set(obj, 12) // stores the reference inside the WeakMap as a key
map.has(obj) // true
map.get(obj) // 12

obj = null /* removes the reference. Will also remove it from the WeakMap because there are no other references to this object */

map.has(obj) // false
map.get(obj) // undefined
console.log(map) // WeakMap {}

// obj is gone from the WeakMap

注意:这仅在将对象存储为而不是值时才有效。如果对象存储为值而所有其他引用都消失,则它不会从WeakMap中消失。弱映射键是弱引用,而不是弱映射值。

您还可以使用数组数组初始化WeakMap,就像Map一样。不同之处在于,因为每个键必须是一个对象,所以每个数组中的第一个项必须是一个对象。如果您尝试将非对象键放在WeakMap中,则会引发错误。

注意:WeakMap没有size属性

弱地图用例

WeakMap的一个可能用例可能是在跟踪DOM元素时。通过使用WeakMap,您可以将DOM元素存储为键。删除元素后,对象将被垃圾收集以释放内存。

const map = new WeakMap()
const element = document.querySelector(".button")

map.set(element, "Buttons")

map.get(element) // "Buttons"

element.parentNode.removeChild(element) // remove the element
element = null // removes reference

// WeakMap now empty!

WeakMap的另一个实际用途是存储私有对象数据。ES6中的所有对象属性都是公共的。那你怎么去呢?在ES5中,您可以执行以下操作:

var Car = (function(){

    var privateCarsData = {}
    var privateId = 0

    function Car(name, color){
        Object.defineProperty(this, "_id", {value: privateId++})

        privateCarsData[this._id] = {
            name: name,
            color: color
        }
    }

    Car.prototype.getCarName = function(){
        return privateCarsData[this._id].name
    }

    Car.prototype.getCarColor = function(){
        return privateCarsData[this._id].color
    }

    return Car
}())

这与您在ES5中拥有真正的私有数据非常接近。这里,Car定义包含在Immediately Invoked Function Expression(IIFE)中。我们有两个私有变量,privateCarsData和privateId。privateCarsData存储每个Car实例的私有信息,privateId为每个实例生成唯一的id。

当我们调用时Car(name, color),_id属性被添加到privateCarsData中,并且它接收具有名称和颜色属性的对象。getCarNamegetCarColor使用this._id作为键来检索数据。

数据是安全的,因为在IIFE之外无法访问privateCarsData,但是._id暴露出来。问题是没有办法知道Car实例何时被销毁。因此,当实例消失时,我们无法正确更新privateCarsData,并且它将始终包含额外数据。

const Car = (function(){

    const privateCarsData = new WeakMap()

    function Car(name, color){
        // this => Car instance
        privateCarsData.set(this, {name, color})
    }

    Car.prototype.getCarName = function(){
        return privateCarsData.get(this).name
    }

    Car.prototype.getCarColor = function(){
        return privateCarsData.get(this).color
    }

    return Car
}())

此版本使用WeakMap作为privateCarsData而不是对象。我们将使用Car实例作为键,因此我们不需要为每个实例生成唯一的id。键将是this,值是包含名称和颜色的对象。getCarNamegetCarColor通过传递this给get方法来检索值。现在,每当Car实例被销毁时,引用privateCarsData中的该实例的密钥将被垃圾收集以释放内存。

结论

任何时候您只想使用对象键,弱地图将是您的最佳选择。将优化内存并避免内存泄漏。但是,弱地图使您几乎无法了解它们的内容。您不能使用forEach(),没有size属性和clear()方法。如果需要检查内容,请使用常规地图。显然,如果你想使用非对象键,你也必须使用常规地图。