ES6 Map和WeakMap

117 阅读4分钟

Map

  • 类似于对象,是键值对的集合(Hash 结构)

  • “键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

生成Map实例

const map1 = new Map();
const map2 = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

Map 实例的属性

  • Map.prototype.size:返回Map实例的成员总数

实例的方法

操作方法

  • Map.prototype.set(key,value)

    • 设置键名key对应的键值为value

    • 返回整个 Map 结构

    • 如果key已经有值,则键值会被更新,否则就新生成该键

  • Map.prototype.get(key)

    • 读取key对应的键值

    • 如果找不到key,返回undefined

  • Map.prototype.has(key)

    • 某个键是否在当前 Map 对象之中

    • 返回一个布尔值

  • Map.prototype.delete(key)

    • 删除某个键,返回true

    • 删除失败,返回false

  • Map.prototype.clear()

    • 清除所有成员,没有返回值

遍历方法

  • Map.prototype.keys():返回键名遍历器

  • Map.prototype.values():返回键值遍历器

  • Map.prototype.entries():返回键值对遍历器

  • Map.prototype.forEach():使用回调函数遍历每个成员

  • 注意

    • Map类型具备有序性,遍历的顺序即元素的插入顺序

    • Map类型同样可以使用for of、for in进行遍历

      • for…of:等同于使用map.entries()

互相转化

数组转为Map

let arr = [
  ['name', '张三'],
  ['info', { height: 175, sex: '男' }],
]

let map = new Map(arr) // {'name' => '张三','info' => {height:175, sex:'男'}}

对象转为Map

let obj = { name: '张三', sex: '男' }
let map = new Map()
for (let key in obj) {
  map.set(key, obj[key])
}

Map转为数组

  • 直接使用扩展运算符

  • 使用Array.from函数

let map = new Map([
  ['name', '张三'],
  ['info', { height: 175, sex: '男' }],
])

let arr1 = [...map] // [['name','张三'],['info',{height:175, sex:'男' }]
let arr2 = Array.form(map) // [['name','张三'],['info',{height:175, sex:'男' }]
// 两种方式的值相同

Map转为对象

  • 如果Map的键名都为字符串,则可以直接通过循环给对象添加键值对

  • 若键名存在非字符串类型,则需要先将键名转化为字符串

let map = new Map([
  ['name', '张三'],
  [{ name: 'jack' }, { height: 175, sex: '男' }],
])

let obj = {}
map.forEach((item, key) => {
  if (typeof key != 'string') {
    key = JSON.stringify(key)
  }
  obj[key] = item
})
obj // {'name': '张三', "{'name':'jack'}": {'height':175, 'sex':'男'}}

实例1:扩展对象

  • 记录一系列对象中每个对象一种属性

  • 假设有100只鸡,需要记录每只鸡的重量,有两种思路

    1. 想办法用笔写到鸡身上

    2. 记录到一个本本上

class Chicken {
}
// 100只鸡
let chickenList = []
for (let i = 0; i < 100; i++) {
  chickenList.push(new Chicken())
}
                   
// 方法1:记录到鸡身上
chickenList.forEach(function(chicken, index){
	chicken.weight = getWeight(chicken);
});

// 方法2:记录到本本上
let notebook = [];
chickenList.forEach(function(chicken, index){
	notebook[index] = getWeight(chicken);
});
  • 第1种思路存在以下问题

    1. 破坏了鸡的卖相

      • 修改了原有对象,可能导致意外的行为

      • 比如你想把一只5斤的鸡当成6斤卖出去,结果鸡身上直接写“我只有5斤”

    2. 可能碰到一些战斗鸡,一个字都写不上去

      • 对象冻结了或者有不可覆盖的属性
    3. 可能写到一些本来就写了字的地方,导致根本看不清

      • 与对象原有属性冲突
  • 第2种方法,存在以下问题

    1. 本本无法和鸡精准地一一对应,只能靠一些索引或者标记(例如给每只鸡起一个名字)去(不可靠)地记录对应关系

      • 无法精准地对比到是哪一个对象
  • Map扩展对象

// 记录到另一个本本上
let notebook = new Map();
chickenList.forEach(function(chicken, index){
	notebook.set(chicken, getWeight(chicken));
});

实例2:完善私有属性的实现

  • Symbol实现的私有属性仍存在可以被特殊api遍历的缺陷

  • 用一个闭包内的Map来扩展每个生成的对象

var Person = (function() {
  var map = new Map();

  function Person(name) {
    map.set(this,name);
  }

  Person.prototype.getName = function() {
    return map.get(this);
  };

  return Person;
}());

WeakMap

  • WeakMap的键只能是对象,而不能是其他类型的值

  • WeakMap 中对键的引用是弱引用

  • WeakMap 不能遍历

    • 弱引用的只是键名,而不是键值,键值依然是正常引用

WeakMap只有四个方法可用:get()set()has()delete()

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)

实例:完善私有属性的实现

  • 前面基于Map的实现存在问题

    • Person实例的外部引用消除时,闭包中的Map仍然有Person实例作为键的引用,Person实例不会被垃圾回收

    • 必须等到所有的Person实例的外部引用消除,Map所在的闭包也会消除,最后Person实例才会被垃圾回收

  • WeakMap进一步完善

var Person = (function() {
  var wm = new WeakMap();

  function Person(name) {
    wm.set(this,name);
  }

  Person.prototype.getName = function() {
    return wm.get(this);
  };

  return Person;
}());