一文搞懂ES6的Map

Posted javascript艺术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞懂ES6的Map相关的知识,希望对你有一定的参考价值。

什么是Map

Map是ECMAScript 6 的新增特性,是一种新的集合类型,为javascript带来了真正的键/值存储机
制。

  • Map 对象存有键值对,其中的键可以是任何数据类型。

  • Map 对象记得键的原始插入顺序。

  • Map 对象具有表示映射大小的属性。

Map的基本API

1、new Map() 创建新的 Map 对象

使用 new 关键字和 Map 构造函数可以创建一个空映射:

// 创建新的 Map 对象。
const m = new Map();

如果想在创建的同时初始化实例,可以给 Map 构造函数传入一个可迭代对象,需要包含键/值对数
组。可迭代对象中的每个键/值对都会按照迭代顺序插入到新映射实例中

const m1 = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m1.size)  // 3
console.log(m1)

console打印出的m1如下:

使用自定义迭代器初始化映射

// 使用自定义迭代器初始化映射
const m2 = new Map( 
 [Symbol.iterator]: function*()  
 yield ["key1", "val1"]; 
 yield ["key2", "val2"]; 
 yield ["key3", "val3"]; 
  
); 
alert(m2.size); // 3
console.log(m2)

console打印出的m2如下:

set()、 get()、has()、delete()、clear()

初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可
以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。

  • set()方法再添加键/值对
  • get()获取 Map 对象中键的值
  • has()进行查询
  • delete()删除一个键/值对
  • clear()清除这个映射实例中的所有键/值对
const m = new Map(); 
alert(m.has("firstName")); // false 
alert(m.get("firstName")); // undefined 
alert(m.size); // 0 
m.set("firstName", "Matt") 
 .set("lastName", "Frisbie"); 
alert(m.has("firstName")); // true 
alert(m.get("firstName")); // Matt 
alert(m.size); // 2 
m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // true 
alert(m.size); // 1 
m.clear(); // 清除这个映射实例中的所有键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // false 
alert(m.size); // 0

set()方法返回映射实例,因此可以把多个操作连缀起来,包括初始化声明:

const m = new Map().set("key1", "val1");
m.set("key2", "val2")
    .set("key3", "val3");
alert(m.size); // 3

与 Object 只能使用数值、字符串或符号作为键不同,Map 可以使用任何 JavaScript 数据类型作为
键。

const m = new Map(); 
const functionKey = function() ; 
const symbolKey = Symbol(); 
const objectKey = new Object(); 
m.set(functionKey, "functionValue"); 
m.set(symbolKey, "symbolValue"); 
m.set(objectKey, "objectValue"); 
alert(m.get(functionKey)); // functionValue 
alert(m.get(symbolKey)); // symbolValue 
alert(m.get(objectKey)); // objectValue

Map的顺序与迭代

与 Object 类型的一个主要差异是,Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执
行迭代操作。
Map映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。

entries() 返回 Map 对象中键/值对的数组。

Map映射实例提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以
通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器:

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m.entries === m[Symbol.iterator]); // true 
for (let pair of m.entries())  
 alert(pair); 
 
// [key1,val1] 
// [key2,val2] 
// [key3,val3] 
for (let pair of m[Symbol.iterator]())  
 alert(pair); 
 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]

因为 entries()是默认迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组:

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]

如果不使用迭代器,而是使用回调方式,则可以调用映射的 forEach(callback, opt_thisArg)
方法并传入回调,依次迭代每个键/值对。

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
m.forEach((val, key) => alert(`$key -> $val`)); 
// key1 -> val1 
// key2 -> val2 
// key3 -> val3
keys()和 values()分别返回以插入顺序生成键和值
const m = new Map([
    ["key1", "val1"],
    ["key2", "val2"],
    ["key3", "val3"]
]);
for (let key of m.keys()) 
    alert(key);

// key1 
// key2 
// key3 
for (let key of m.values()) 
    alert(key);

// value1 
// value2 
// value3

选择object还是map

Map 的大多数特性都可以通过 Object 类型实现,但二者之间还是存在一些细微的差异。
对于大部分业务开发者来说,选择object还是map只是个人喜好问题,其实影响不大。
但是对于追求业务和性能的开发者来说,object和map确实存在很大的区别。
在具体实践中使用哪一个,还是值得细细甄别。

1. 内存占用

同一浏览器中给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。

2. 插入性能

向 Object 和 Map 中插入新键/值对的消耗大致相同,不过插入 Map 在所有浏览器中一般会稍微快
一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操
作,那么显然 Map 的性能更佳。

3. 查找速度

与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,
则 Object 有时候速度更快。如果代码涉及大量查找操作,那么某些情况下可能选
择 Object 更好一些。

4、删除性能

对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。
如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。

文章参考自《JavaScript高级程序设计第四版》

欢迎关注我的个人微信公众号:javascript艺术

都2021年了,再不学ES6你就out了 —— 一文搞懂ES6

导语:ES6是什么?用来做什么?

  • ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版

  • 虽然15年就有正式版本了,但是国内普遍商用是在2018年之后去了,甚至到现在有很多前端仍然搞不懂ES6(都2021年了,兄dei~)
    在这里插入图片描述

  • ES6 的出现主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念

  • 目前存在少数低版本浏览器的 JavaScript 是 ES5 版本,大多数的浏览器已经支持 ES6

  • ES6提供了大量的语法糖,让你写代码的时候简直不要太爽!

  • 你必须要知道的是:现在各企业都普遍使用,不会ES6意味着你很难找到工作,上班了你连别人的代码都看不懂
    在这里插入图片描述

1. let 与 const

1.1 let 与 var

  • let:ES6新增,用于声明变量,有块级作用域
  • var:ES5中用于声明变量的关键字,存在各种问题(例如:红杏出墙~)
  • 如果你的代码里还存在 var,那你的良心就大大的坏了!

var存在的问题:

 // 1.声明提升
 // 此处会正常打印,但这是错误的(属于先上车后买票了!)
 console.log(name); 
 var name = "大帅比";
 
 // 2. 变量覆盖
 var demo = "小明";
 var demo = "小红";
 // 此处会打印小红,这也是错误的(属于套牌车,违法的啊,兄弟)
 // 同一个项目中,发生变量覆盖可能会导致数据丢失以及各种不可预知的bug,原则上来说:变量不能重名
 console.log(demo)

// 3. 没有块级作用域
  function fn2(){
      for(var i = 0; i < 5; i++){
          // do something
      }
      // 此处会正常打印出 i 的值,这是错误的
      // i是定义在循环体之内的,只能在循环体内打印,当前现象叫做红杏出墙!!!
      console.log(i);
  }
  fn2();

let不会存在上述问题:

 // 1. 不会存在声明提前
 // 此处会报错(这里必须报错,原则上来说不能先上车后买票)
 console.log(name); 
 let name = "大帅比";
 
 // 2. 不会有变量覆盖
 let demo = "小明";
 let demo = "小红";
 // 此处会报错(不能使用套牌车!)告诉你已经定义了此变量。避免了项目中存在变量覆盖的问题
 console.log(demo)

// 3. 有块级作用域
  function fn2(){
      for(let i = 0; i < 5; i++){
          // do something
      }
      // 此处会报错,无法打印,防止红杏出墙!!!
      // i是定义在循环体之内的,循环体外当然无法打印 
      console.log(i);
  }
  fn2();

const

  • const 声明一个只读的常量,一旦声明,常量的值就不能改变
  • 一般用于全局变量
  • 通常变量名全部大写(请按照规则来,不要乱搞,容易出事情)
const PI = "3.1415926";

2. 解构赋值

  • 解构赋值是对赋值运算符的扩展
  • 针对数组或者对象进行模式匹配,然后对其中的变量进行赋值
  • 代码简洁且易读,语义更加清晰明了,方便了复杂对象中数据字段获取(简而言之:用起来很爽!

在这里插入图片描述

2.1 用在数组上

let [a, b, c] = [1, 2, 3];
// a = 1,b = 2,c = 3 相当于重新定义了变量a,b,c,取值也更加方便

// , = 占位符
let arr = ["小明", "小花", "小鱼", "小猪"];
let [,,one] = arr; // 这里会取到小鱼

// 解构整个数组
let strArr = [...arr];
// 得到整个数组
console.log(strArr);

2.2 用在对象上

let obj = {
   className : "卡西诺",
   age: 18
}
let {className} = obj; // 得到卡西诺
let {age} = obj;	// 得到18

// 剩余运算符
let {a, b, ...demo} = {a: 1, b: 2, c: 3, d: 4};
// a = 1
// b = 2
// demo = {c: 3, d: 4}

3. 模板字符串

  • 模板字符串相当于加强版的字符串,用反引号 ``
  • 除了作为普通字符串,还可以用来定义多行字符串,可以在字符串中加入变量和表达式

3.1 普通字符串

// 普通字符串
let string = "hello"+"小兄弟"; // hello小兄弟
// 如果想要换行
let string = "hello'\\n'小兄弟"
// hello
// 小兄弟

3.2 模板字符串

let str1  = "穿堂而过的";
let str2 = "风";
// 模板字符串
let newStr = `我是${str1}${str2}`;
// 我是穿堂而过的风
console.log(newStr)

// 字符串中调用方法
function fn3(){
  return "帅的不行!";
}
let string2= `我真是${fn3 ()}`;
console.log(string2);  // 我真是帅的不行!

4. ES6 函数(升级后更爽)

4.1 箭头函数

  • 箭头函数是一种更加简洁的函数书写方式
  • 箭头函数本身没有作用域(无this)
  • 箭头函数的this指向上一层,上下文决定其this
  • 基本语法:参数 => 函数体

a. 基本用法

let fn = v => v;
//等价于
let fn = function(num){
 return num;
}
fn(100);  // 输出100

b. 带参数的写法

let fn2 = (num1,num2) => {
 let result = num1 + num2;
 return result;
}
fn2(3,2);  // 输出5

c. 箭头函数中的this指向问题

  • 箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。在函数定义的时候就已经决定了
function fn3(){
  setTimeout(()=>{
    // 定义时,this 绑定的是 fn3 中的 this 对象
    console.log(this.a);
  },0)
}
var a = 10;
// fn3 的 this 对象为 {a: 10},因为它指向全局: window.a
fn3.call({a: 18});  // 改变this指向,此时 a = 18

d. 箭头函数适用的场景

  • 当我们代码里存在这样的代码:let self = this;
  • 需要新建变量去保存this的时候
  • 案例如下:
let Person1 = {
    'age': 18,
    'sayHello': function () {
      setTimeout(()=>{
        console.log(this.age);
      });
    }
};
var age = 20;
Person1.sayHello();  // 18

4.2 函数参数的扩展

1. 默认参数

// num为默认参数,如果不传,则默认为10
function fn(type, num=10){
 console.log(type, num);
}
fn(1);	// 打印 1,10
fn(1,2); // 打印 1,2 (此值会覆盖默认参数10)
  • 需要注意的是:只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递

2. 不定参数

// 此处的values是不定的,且无论你传多少个
function f(...values){
    console.log(values.length);
}
f(1,2);      // 2
f(1,2,3,4);  // 4

在这里插入图片描述

5. Class类

  • class (类)作为对象的模板被引入,可以通过 class 关键字定义类
  • class 的本质是 function,同样可以看成一个块
  • 可以看作一个语法糖,让对象原型的写法更加清晰
  • 更加标准的面向对象编程语法

5.1 类的定义

// 匿名类
let Demo = class {
    constructor(a) {
        this.a = a;
    }
}
// 命名类
let Demo = class Demo {
    constructor(a) {
        this.a = a;
    }
}

5.2 类的声明

class Demo {
    constructor(a) {
        this.a = a;
    }
}
  • 请注意,类不能重复声明
  • 类定义不会被提升,必须在访问前对类进行定义,否则就会报错。
  • 类中方法不需要 function 关键字。
  • 方法间不能加分号

5.3 类的主体

  • 公共属性(依然可以定义在原型上)
class Demo{}
Demo.prototype.a = 2;
  • 实例属性
class Demo {
    a = 2;
    constructor () {
        console.log(this.a);
    }
}
  • 方法:constructor
class Demo{
    constructor(){
      console.log('我是构造器');
    }
}
new Demo(); // 我是构造器

如果不写constructor,也会默认添加

5.4 实例化对象

class Demo {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Demo');
    }
    sum() {
        return this.a + this.b;
    }
}
let demo1 = new Demo(2, 1);
let demo2 = new Demo(3, 1);
// 两者原型链是相等的
console.log(demo1._proto_ == demo2._proto_); // true
 
demo1._proto_.sub = function() {
    return this.a - this.b;
}
console.log(demo1.sub()); // 1
console.log(demo2.sub()); // 2

6. Map()

6.1 Maps 和 Objects 的区别

  • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算
    在这里插入图片描述

6.2 Map中的key

// 1. key是字符串
let myMap = new Map();
let keyString = "string"; 
 
myMap.set(keyString, "和键'string'关联的值");

// keyString === 'string'
myMap.get(keyString);    // "和键'string'关联的值"
myMap.get("string");   // "和键'string'关联的值"

// 2.key是对象
let myMap = new Map();
let keyObj = {}, 
 
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.get({}); // undefined, 因为 keyObj !== {}

// 3. key也可以是函数或者NaN                         

6.3 Map 的迭代

// 1.使用 forEach
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
 
// 0 = zero , 1 = one
myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
}, myMap)

// 2. 也可以使用 for...of

6.4 Map 与 Array的转换

letkvArray = [["key1", "value1"], ["key2", "value2"]];
 
// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
let myMap = new Map(kvArray);
 
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
let outArray = Array.from(myMap);

6.5 关于map的重点面试题

  • 请谈一下 Map和ForEach 的区别(问到map,必定问到此题)

详细解析:

  1. forEach()方法不会返回执行结果,而是undefined
  2. map()方法会得到一个新的数组并返回
  3. 同样的一组数组,map()的执行速度优于 forEach()(map() 底层做了深度优化

性质决定了两者应用场景的不同

  • forEach() 适合于你并不打算改变数据的时候,而只是想用数据做一些事情(比如存入数据库)
let arr = ['a', 'b', 'c', 'd'];
arr.forEach((val) => {
 console.log(val); // 依次打印出 a,b,c,d
});
  • map() 适用于你要改变数据值的时候,它更快,而且返回一个新的数组
let arr = [1, 2, 3, 4, 5];
let arr2 = arr.map(num => num * 2).filter(num => num > 5);
// arr2 = [6, 8, 10]
  • 创作不易,深夜肝文,如果对大家有帮助,还请大家支持一波~
  • 点赞,关注,收藏走一波,感激不尽!
  • 好人一生平安,一胎生八个!

感谢大家的支持

以上是关于一文搞懂ES6的Map的主要内容,如果未能解决你的问题,请参考以下文章

都2021年了,再不学ES6你就out了 —— 一文搞懂ES6

都2021年了,再不学ES6你就out了 —— 一文搞懂ES6

一文搞懂JavaScript高阶函数

一文搞懂JavaScript垃圾回收机制

一文搞懂JavaScript垃圾回收机制

一文搞懂JavaScript垃圾回收机制