LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

只会用Object?我想推荐你试试Map

freeflydom
2023年10月25日 15:18 本文热度 366

导读

Javascript中,我们经常使用Object对象来实现键值对存储的功能,但是Object有一些缺点和局限性。为了解决这些问题,ES6引入了一个新的数据结构:MapMap是一个有序的键值对集合,它可以存储任意类型的键和值,并且提供了许多便捷的方法。

本文将介绍MapObject的基本用法和区别,并且说明为什么更推荐大家使用Map


对于Object来说,增删键值对的性能比较差。而在MDN中,特别提到Map对象对增删键值对的操作进行了优化,如图所示:

为什么

这与Javascript虚拟机优化对象的方式有关,虚拟机通过假定对象的结构来优化代码执行效率。而Map是专门用于哈希映射的,其中键值是动态且不断变化的。那么至于为什么没有对Object对象增删键进行优化的原因感兴趣的小伙伴可以阅读这篇文章

可迭代性

首先,首先,Object没有实现迭代协议,所以for…of语句并不能直接迭代对象。这一点在MDN中也有说明:

对象

对象的遍历

由于对象不能使用使用for...of,所以我们只能使用for...in来遍历,但是使用for...in的话,会有如下问题:

  • 会遍历对象所有的可枚举属性,包括原型链上的属性

  • 遍历顺序不一定按照对象属性定义的顺序

  • 遍历的索引为字符串类型的数字,并不能直接进行计算

下面就举例说明一下这些问题

1. 会遍历对象所有的可枚举属性,包括原型链上的属性,例如

function Person(name) {

  this.name = name;

}


Person.prototype.sayHello = function() {

  console.log("Hello, I'm " + this.name);

};


var obj = new Person("Tom");


for (const prop in obj) {

    console.log(prop) // name, sayHello

}


可以看到obj除了自身属性name之外,还会遍历到原型链上的属性sayHello。这是因为sayHello可枚举的。
所以,通常我们需要这样这样写:

// 方法一:使用hasOwnProperty()

for (var prop in obj) {

  if (obj.hasOwnProperty(prop)) {

    console.log(prop); // name

  }

}


// 方法二:使用Object.keys()

var keys = Object.keys(obj);

for (var i = 0; i < keys.length; i++) {

  console.log(keys[i]); // name

}


2. 遍历顺序不一定按照对象属性定义的顺序,例如:

const obj = {

  a: 1,

  b: 2,

  c: 3,

  "1": "one",

  "2": "two",

  "3": "three"

};


for (var key in obj) {

  console.log(key + ": " + obj[key]);

}


这个在谷歌浏览器中会先遍历数字类型的属性,如图:

3. 遍历的索引为字符串类型的数字,并不能直接进行计算,例如

const obj = {

  1: "one",

  2: "two",

  3: "three"

};


for (const key in obj) {

  console.log(key, typeof key); // 1 string; 2 string; 3 string

}


const arr = [4, 5, 6]

for (const index in arr) {

 console.log(index, typeof index); // 0 string; 1 string; 2 string;

}


Map

如果是Map,你可以使用标准的for循环、标准的迭代器和使用解构来获取keyvalue,例如:

const map1 = new Map();


map1.set('a', 1);

map1.set('b', 2);

map1.set('c', 3);


for (const [key, value] of map1) {

 console.log(key, value) // a 1; b 2; c 3

}


对于对象,我们还有一个Object.entries() 来做类似的事情,尽管它看起来不是那么流行,但确实可以

const myObject = {a: 1, b: 2, c: 3}


for (const [key, value] of Object.entries(myObject)) {

 console.log(key, value) // // a 1; b 2; c 3

}


对于Map,你有更简单的办法直接内置迭代:

// 你可以只便利values,keys


for (const value of myMap.values()) {

 console.log(value)

}


for (const key of myMap.keys()) {

 console.log(key)

}


key

内置key

当我们这样创建一个对象时:

const myMap = {}


myMap.valueOf // => [Function: valueOf]

myMap.toString // => [Function: toString]

myMap.hasOwnProperty // => [Function: hasOwnProperty]

myMap.isPrototypeOf // => [Function: isPrototypeOf]

myMap.propertyIsEnumerable // => [Function: propertyIsEnumerable]

myMap.toLocaleString // => [Function: toLocaleString]

myMap.constructor // => [Function: Object]


尽管对象看起来是个空的,你也可以访问这些属性,在MDN中也提到了这个问题:

key的顺序

Map保留了键的顺序,我们可以根据它明确的顺序,直接结构出键值:

const [[firstKey, firstValue]] = myMap


实现LRU缓存

用此特性,我们可以实现一个O(1)LRU缓存

什么是LRU缓存?
LRU缓存是一种缓存淘汰策略,它的全称是Least Recently Used,意思是最近最少使用。它的原理是认为最近使用过的数据应该是有用的,而很久没用过的数据应该是无用的,所以当缓存满了时,就优先删除那些很久没用过的数据,给新数据腾出空间
那么我们用Map实现一下LRU缓存

class LRUCache {

  constructor(capacity) {

    this.capacity = capacity; // 缓存容量

    this.map = new Map(); // 使用Map存储键值对

  }


  // 获取键对应的值,如果不存在则返回 -1

  get(key) {

    if (this.map.has(key)) {

      let value = this.map.get(key);

      this.map.delete(key); // 删除该键值对

      this.map.set(key, value); // 将该键值对重新插入到Map末尾,表示最近使用过

      return value;

    } else {

      return -1;

    }

  }


  // 设置或更新键和值,如果超过缓存容量,则删除最久未使用的键值对

  put(key, value) {

    if (this.map.has(key)) {

      this.map.delete(key);

    } else if (this.map.size >= this.capacity) { // 如果Map中没有该键,且已达到缓存容量上限

      let oldestKey = this.map.keys().next().value; // 获取Map中第一个(最久未使用)的键

      this.map.delete(oldestKey);

    }

    this.map.set(key, value); // 将新的或更新的键值对插入到Map末尾,表示最近使用过 

  }

}


key的类型

Map甚至可以做一些对象实现不了的事情:

myMap.set({}, value)

myMap.set([], value)

myMap.set(document.body, value)

myMap.set(function() {}, value)

myMap.set(myDog, value)


Object中,key必须是字符串、数字或者Symbol类型,而在Map中则可以是任何类型,包括函数、对象或者任何原始值。这意味着,在Map中,我们可以用一个Object来作为一个元素的key

复制与转换

复制

你可能会觉得对象更容易复制,比如:

const copied = {...myObject}

const copied = Object.assign({}, myObject)


但,实际上Map也容易复制:

js复制代码const copied = new Map(myMap)


同样,你还可以使用structuredClone 深拷贝:

const copied = new Map(myMap)


转换

// Map转对象

const myObj = Object.fromEntries(myMap)


// 对象转Map

const myMap = new Map(Object.entries(myObj))


因此,我们可以不使用元组构造映射,可以将它们构造成对象,这样看起来更加美观:

const myMap = new Map([['key', 'value'], ['keyTwo', 'valueTwo']])


// 可以写成

const myMao = new Map(Object.entries({

    key: 'value',

    keyTwo: 'valueTwo'

}))


序列化与反序列化

现在您可能会说普通对象和数组相对于映射和集合还有最后一个优势 — 序列化JSON.stringify()JSON.parse()

但是,当我们使用时,有个第二个参数传null: JSON.stringify(obj, null, 2) ,为什么呢?
它被称为替换器,它允许我们定义任何自定义类型应该如何序列化。所以,我们可以轻松的将Map进行序列化:

JSON.stringify(obj, (key, value) => {

  // Convert maps to plain objects

  if (value instanceof Map) {

    return Object.fromEntries(value)

  }

  return value

})


我们还可以用相反的方式将对象转回Map:

JSON.parse(string, (key, value) => {

  if (value && typeof value === 'object') {

    return new Map(Object.entries(value))

  }

  return value

})



查看原文



该文章在 2023/10/25 15:18:57 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved