ES6知识点归纳(三)——Promise、Symbol、模块(Modules)、class继承、Iterator、Generator、Proxy、Set、WeakSet、Map、WeakMap

参考书籍:http://es6.ruanyifeng.com/
参考视频:https://www.bilibili.com/video/av47304735
全部篇章:
ES6知识点归纳(一)——var、let、const、箭头函数、函数参数的默认值、ES6模版字符串
ES6知识点归纳(二)——对象解构、数组解构、for of循环、新增的方法、剩余参数、扩展运算符
ES6知识点归纳(三)——Promise、Symbol、模块(Modules)、class继承、Iterator、Generator、Proxy、Set、WeakSet、Map、WeakMap

文章目录

  • Promise
    • axios(promise 库)
    • 编写一个简单的 promise
    • 处理多个 Promise
  • Symbol
  • 模块(Modules)
  • class 继承语法糖
    • 原型继承
    • class 语法
    • class 继承
    • 扩展内建对象 Array
  • Iterator(遍历器/迭代器)
  • Generator(生成器)
    • 简单语法
    • 其他例子
    • Generator应用
  • Proxy
    • 什么是Proxy
    • Proxy应用例子
  • Set
  • WeakSet
  • Map
  • WeakMap


Promise

  • 在向服务器发起请求时,经常会有这样的需求,就是第一个请求成功后再发起第二个请求,第二个成功后再发起第三个,而为了实现,通常会把第二个请求放在第一个请求的回调函数中,第三个请求则放在第二个请求的回调函数中。
  • 弊端:这样写,随着相关的依赖越来越多,代码嵌套程度越来越深,代码越来越难看,这样很可能会陷入回调地狱(函数作为参数层层嵌套)中。

    其他资料:https://www.jianshu.com/p/39adf6ab8ad1

axios(promise 库)

let userName;
const usersPromise = axios.get("https://api.github.com/users");
usersPromise
  .then(response => {
    console.log(response);
    userName = response.data[0].login;
    return axios.get(`https://api.github.com/users/${userName}/repos`);
  })
  .then(res => {
    console.log(res.data);
  })
  .catch(err => {
    console.error(err);
  });
  • them 相当于一个监听
  • catch 用于监听错误的发生,用了 catch,发生错误时会告诉你发生了错误,没用,则会反馈说 Uncaught Error

编写一个简单的 promise

  • Promise 构造函数的参数是一个函数,这个函数有两个内置参数 resolve,reject
const p = new Promise((resolve, reject) => {
  resolve("success"); //成功就立即返回success
});

//监听这个promise
p.then(data => {
  console.log(data);
}); //打印返回的数据
const p = new Promise((resolve, reject) => {
  reject(Error("failed")); //此处加Error()可以指明错误发生在这行
});

//监听这个promise
p.then(data => {
  console.log(data);
}).catch(err => {
  console.error(err);
});
//不用catch会报Uncaught (in promise) failed

处理多个 Promise

  • .all 方法,只有传入的所有 promise 结果都是 resolve,才会触发 then,只要有一个 promise 的结果是 reject,就会执行 catch
const usersPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(["uOne", "uTwo"]);
  }, 2000);
});
const moviePromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve([{ name: "hi", rating: 9.2 }]);
    reject(Error("failed"));
  }, 500);
});
Promise.all([usersPromise, moviePromise])
  .then(res => {
    console.log(res); //结果的数组与传入的数组顺序一样
  })
  .catch(err => {
    console.log(err);
  });
  • .race 方法的状态由第一个返回的 promise 决定,第一个返回的 promise 结果是什么,那么整个 Promise.race 的结果就是什么,第一个返回的 promise 是 resolve,就执行.then,否则执行 catch
const usersPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(["uOne", "uTwo"]);
  }, 2000);
});
const moviePromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(Error("failed"));
  }, 500); //此处时间较短,先返回moviePromise
});
Promise.race([usersPromise, moviePromise])
  .then(res => {
    console.log(res); //结果的数组与传入的数组顺序一样
  })
  .catch(err => {
    console.log(err);
  });
//最后结果Error: failed,因为第一个返回的结果是moviePromise
const usersPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(Error("failed"));
  }, 2000);
});
const moviePromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve([{ name: "hi", rating: 9.2 }]);
  }, 500); //此处时间较短,先返回moviePromise
});
Promise.race([usersPromise, moviePromise])
  .then(res => {
    console.log(res); //结果的数组与传入的数组顺序一样
  })
  .catch(err => {
    console.log(err);
  });
//最后结果打印了moviePromise返回的结果

Symbol

来看一下这样一种情景

const classRoom = {
  Alfred: { grade: 80, gender: "male" },
  Raymond: { grade: 100, gender: "male" },
  Raymond: { grade: 150, gender: "male" }
};
console.log(classRoom);
//由于两个Raymond的属性名重复,因此后面那个会覆盖前面的一个
  • ES6 中的新数据类型
  • Symbol 用于生成唯一的标志符来避免命名冲突,每个 symbol 都是不一样的
  • typeof 可判断一个变量是不是 Symbol 类型
const greyson = Symbol();
const student = Symbol();

console.log(greyson); //Symbol()
console.log(student); //Symbol()
console.log(typeof greyson, typeof student); //symbol symbol

console.log(greyson == student); //false
console.log(greyson === student); //false
  • 为了更好的调试,可以在定义 symbol 时添加描述
const greyson = Symbol("greyson");
const student = Symbol("student");

console.log(greyson); //Symbol(greyson)
console.log(student); //Symbol(student)

因此,上方的情景可以改写为

const classRoom = {
  [Symbol("Alfred")]: { grade: 80, gender: "male" },
  [Symbol("Raymond")]: { grade: 100, gender: "male" },
  [Symbol("Raymond")]: { grade: 150, gender: "male" }
};
console.log(classRoom);
  • 用 Symbol 定义的属性不能遍历,因此可以用它作为私有属性,在对象内部使用
  • 可用Object.getOwnPropertySymbols()来访问
const classRoom = {
  [Symbol("Alfred")]: { grade: 80, gender: "male" },
  [Symbol("Raymond")]: { grade: 100, gender: "male" },
  [Symbol("Raymond")]: { grade: 150, gender: "male" }
};

for (var key in classRoom) {
  console.log(key); //什么都没打印
}

console.log(Object.keys(classRoom)); //空数组
console.log(Object.getOwnPropertyNames(classRoom)); //空数组

//正确打印classRoom
const syms = Object.getOwnPropertySymbols(classRoom);
console.log(syms);

//获取属性值
const syms2 = Object.getOwnPropertySymbols(classRoom).map(
  sym => classRoom[sym]
);
console.log(syms2);
  • 注意:获取属性值处必须使用[],如果使用.访问,sym 代表的 Symbol 会被当成字符串,就等同于使用了classRoom['sym'],最终结果返回的值都是 undefined

模块(Modules)

在过去,我们要把代码模块化的时候,可能需要很多 script 标签,如果引用很多模块的话,一方面会影响访问的速度,另一方面还会定义很多全局变量,可能会导致命名冲突

<body>
  <h1>Hello,world</h1>
  <script src="user.js"></script>
  <script src="stores.js"></script>
</body>

为了结局这一问题 ES6 给我们提供了一个模块机制。

  1. 一个模块可以拥有很多变量/函数/类
  2. 可以通过 export 命令导出它们
  3. 可以在其他文件中通过 import 命令来引用它们,然后在自己的代码当中使用

a.js

//默认导出
const apiKey = "abc123";
export default apiKey;
//命名导出
export const apiKey = "abc123";
export const age = 12;
export function hi() {
  console.log("hi");
}

const apiKey = "abc123";
const age = 12;
function greet() {
  console.log("hi");
}
export { apiKey as Key, age, greet };
//as可以将apiKey命名外另外的你想要导出的名字,引入时要使用as后的名字

b.js

//使用默认导出时
import apiKey from "./a.js"; //这里apiKey是自己命名的,可以修改
console.log(apiKey); //abc123
import { apiKey, age } from "./a.js";

当引入的变量名和模块里已有的变量名冲突的话,可以使用 as 重命名

import { Key as apiKey, age } from "./a.js";

注意

  1. 一个模块中只能有一个默认导出
  2. 使用默认导出的话,在导入时可以随意命名
  3. 使用命名导出的话,在导入时必须使用导出时的名字,且要用大括号引入

app.js

//导入其他包的方法或变量
import { uniq } from "lodash"; //引用lodash里面的uniq方法,这种是命名导出
import moment from "moment"; //导入默认的信息

index.html

<body>
  <h1>Hello,worldh1>
  <script src="./app.js">script>
body>

注意:

  1. 目前浏览器还在完善 ES6 模块的定义,因此目前不能直接在浏览器中使用它,而是需要通过 webpack、gulp 这样一些打包工具来处理。
  2. Babel 可以帮我们把 ES6 代码转换成 ES5 代码,以保证一些还没有完善 ES6 的浏览器能够使用

class 继承语法糖

原型继承

function User(name, email) {
  this.name = name;
  this.email = email;
}
User.prototype.info = function() {
  console.log(`Hi.I'm ${this.name}`);
};
const Alfred = new User("Alfred", "[email protected]");
const Raymond = new User("Raymond", "[email protected]");

//实例化之后重写原型对象属性
User.prototype.info = function() {
  console.log(`Hi.I'm ${this.name},my email is ${this.email}`);
};
User.prototype.description = function() {
  console.log("hi");
};

console.log(Alfred);

class 语法

  1. 类的声明
class User {
  //这个类的内容
}
  1. 类的表达式
const User = class {
  //这个类的内容
};
  1. 类是特殊的函数,但不会提升,不能在类的定义之前调用
  2. ES6 的 class 中,两个方法之间不能添加逗号分隔
  3. 类的静态方法(static 方法)只能通过类名调用,不能通过实例调用
  4. class 内可以定义一个属性的 get 和 set 方法
const User = class {
  //类的构造函数
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  info() {
    console.log(`Hi.I'm ${this.name},my email is ${this.email}`);
  }
  //静态方法
  static description() {
    console.log("description");
  }

  set github(value) {
    this.githubName = value;
  }
  get github() {
    return `https://github.com/${this.githubName}`;
  }
};
const Alfred = new User("Alfred", "[email protected]");
const Raymond = new User("Raymond", "[email protected]");
Alfred.githubName = "Niccce";

// Alfred.description();//Alfred.description is not a function
User.description();

console.log(Alfred);
console.log(Alfred.github);
  1. class 中的方法名可以使用计算属性的格式
let methodName = "info";
const User = class {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  [methodName]() {
    console.log(`Hi.I'm ${this.name},my email is ${this.email}`);
  }
};
const Alfred = new User("Alfred", "[email protected]");
Alfred.info();
  1. class 都要用 new 来调用,不能直接用类名调用

class 继承

class Animal {
  constructor(name) {
    this.name = name;
    this.belly = [];
  }
  eat(food) {
    this.belly.push(food);
    console.log(this.belly);
  }
}
class Dog extends Animal {
  constructor(name, age) {
    super(name); //调用父类的构造函数
    this.age = age;
  }
  bark() {
    console.log("barking");
  }
}
const lucky = new Dog("lucky", 2);
lucky.eat("meat");
console.log(lucky);
  1. 在子类上定义的方法如果跟基类上的方法同名的话,会覆盖基类上的方法
class Animal {
  constructor(name) {
    this.name = name;
    this.belly = [];
  }
  speak() {
    console.log(`Hi,I'm ${this.name}`);
  }
}
class Dog extends Animal {
  constructor(name, age) {
    super(name); //调用父类的构造函数
    this.age = age;
  }
  bark() {
    console.log("barking");
  }
  speak() {
    console.log(`Bark,I'm ${this.name}`);
  }
}
const lucky = new Dog("lucky", 2);
lucky.speak(); //Bark,I'm lucky

扩展内建对象 Array

简单例子

class MyArray extends Array {
  constructor() {
    super();
  }
}
const colors = new MyArray();
colors[0] = "red";
console.log(colors.length); //1
colors.length = 0;
console.log(colors[0]); //undefined

简单运用

class singers extends Array {
  constructor(name, ...item) {
    //在这里把剩余参数都写在了item里
    super(...item); //在这里是把item里的元素都扩展出来
    this.name = name;
  }
  add(singer) {
    this.push(singer);
  }
}
const movies = new singers(
  "favorite singers",
  { name: "Raymond", no: 1 },
  { name: "Alfred", no: 2 },
  { name: "Greyson", no: 3 }
);
movies.push({ name: "Fred", no: 4 });
console.log(movies);
console.table(movies);

Iterator(遍历器/迭代器)

遍历器是一种接口,为各种不同的数据结构提供统一的访问机制。

  1. 任何数据结构,只要部署了 Iterator 接口,就可以完成遍历操作。
  2. Iterator 作用:
  • 为各种数据结构提供统一的、简便的访问接口;
  • 使得数据结构的成员能够按某次序排列;
  • ES6 创造了一种新的遍历命令——for of 循环,Iterator 接口主要供 for of 消费。
  1. 遍历器是一个对象,其中有 next 方法,每次调用 next 方法都会返回数据结构的当前成员信息(value 和 done 两个属性对象),其中 value 属性是当前成员的值,done 属性是一个布尔值,表示变量是否结束。
  2. ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就认为是“可遍历的”。调用 Symbol.iterator 方法,可以得到当前数据结构默认的遍历器生成函数。
  3. for of 循环就是从遍历器中获取到 value 的值,在打印出来
const colors = ["red", "green", "yellow"];
console.log(colors);
const iterator = colors[Symbol.iterator]();
console.log(iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
  • 在 ES6 中有三种类型的集合对象,分别是 Array、Map、Set,针对这三种数据类型,ES6 为它们提供了内建的遍历器,我们可以通过调用相应的方法来获取它们
//colors.entries()这个方法返回的是元素的属性名可属性值
const colors = ["red", "green", "yellow"];
console.log(colors.entries());
const iterator = colors.entries();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
const colors = ["red", "green", "yellow"];
console.log(colors.values());
const iterator = colors.values();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
//colors.keys()返回的是元素的索引值
const colors = ["red", "green", "yellow"];
console.log(colors.keys());
const iterator = colors.keys();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

编写自己的遍历器

Array.prototype.Myvalues = function() {
  let i = 0; //记录当前元素的位置
  let items = this;
  return {
    //这个方法返回一个对象,即遍历器
    next() {
      const done = i >= items.length;
      const value = done ? undefined : items[i++];
      return {
        value,
        done
      };
    }
  };
};
const colors = ["red", "green", "yellow"];
const iterator = colors.Myvalues();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Generator(生成器)

目前 JavaScript 的函数都是从上到下依次执行直到结束,而 Generator 函数可以开始暂停开始暂停,也可以在调用当中传入另外的参数。

简单语法

  1. function 命令与函数名之间有一个*
  2. 函数体内部使用yield语句定义不同的内部状态(‘yield’意思是产出)
  3. Generator 函数在调用后并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器。
  4. 必须调用遍历器的 next 方法,使得指针移向下一个状态。
  5. Generator 函数是分段执行的,yield 语句是暂停执行的标记,而 next 方法可以恢复执行
function* listColors() {
  yield "red"; //有点类似函数的返回值,但这个yield是本次执行这个函数的返回值
  yield "green";
  yield "yellow";
}
const colors = listColors();
console.log(colors); //打印说是一个Generator,状态是suspended
console.log(colors.next());
console.log(colors.next());
console.log(colors.next());
console.log(colors.next());
console.log(colors);//状态编程了closed

其他例子

function* num(){
  let i=0;
  yield i;
  i++;
  yield i;
  i++;
  yield i;
}
const colors=num();
console.log(colors.next());
console.log(colors.next());
console.log(colors.next());
console.log(colors.next());
const singers = [
  { name: "Raymond", no: 1 },
  { name: "Alfred", no: 2 },
  { name: "Greyson", no: 3 }
];
function* loop(arr){
  for(const repo of arr){
    yield repo;
  }
}
const repoGen=loop(singers);
console.log(repoGen);
console.log(repoGen .next());
console.log(repoGen .next());
console.log(repoGen .next());
console.log(repoGen .next());

Generator应用

  1. 用于控制Ajax工作流
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
<script>
  function ajax(url){
    axios.get(url).then(res=>userGen.next(res.data));//4.请求完成后,将所得数据传给next方法继续进行Generator的下一步,得到的数据也会存到users中
  }
  function* steps(){
    const users=yield ajax('https://api.github.com/users');//3.开始请求GitHub用户,此时steps函数暂停了
    console.log(users);
    const firstUser=yield ajax(`https://api.github.com/users/${users[0].login}`);
    console.log(firstUser);
    const followers=yield ajax(firstUser.followers_url);
    console.log(followers);
  }
  const userGen=steps();//1.调用steps生成Generator
  userGen.next();//2.调用Generator的next方法开始执行Generator
  script>

Proxy

什么是Proxy

  1. Proxy能够帮我们重写对象上默认的方法,用于修改某些操作的默认行为
  2. proxy原意为代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”
  3. 语法:var proxy = new Proxy(target,handler);,target是我们要代理的目标对象,handler是一个对象,它包含了我们想要重写的一些操作,这个对象里的方法我们称之为trap,这些方法详见MDN
const person={name:'Alfred',age:30};
const personProxy=new Proxy(person,{
  get(target,key){
    return target[key].toUpperCase();
  },//改变了获取属性值的默认操作
  set(target,key,value){
    if(typeof value ==='string'){
      target[key]=value.trim();//去掉属性值左右的空格
    }
  }
});
personProxy.name='Raymond';
console.log(personProxy);//Proxy {name: "Raymond", age: 30}
console.log(personProxy.name);//RAYMOND
personProxy.sayHi='       Hi,Alfred        ';
console.log(personProxy.sayHi);//HI,ALFRED

Proxy应用例子

  1. 格式化电话号码
const phonenumHandler = {
  set(target, key, value) {
    target[key] = value.match(/[0-9]/g).join("");
  },
  get(target, key) {
    return target[key].replace(/(\d{3})(\d{4})(\d{4})/, "$1-$2-$3");
  }
};
const phoneNum = new Proxy({}, phonenumHandler);
phoneNum.home = "131 2222 3333";
console.log(phoneNum);//Proxy {home: "13122223333"}
console.log(phoneNum.home);//131-2222-3333

match()方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
join()方法用于把数组中的所有元素通过指定的分隔符进行分隔,然后放入一个字符串。
replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

  1. 对用户的操作进行处理或保护
    比如给用户提供了一个对象person,里面有一个id属性,有时候别人可能不会很清楚你的’id’的大小写
const person = { id: 2 };
person.ID = 2;
person.iD = 2;
person.id = 2;

为了避免用户花费很多时间来排除这样一些bug,我们可以通过proxy来对用户的操作进行处理或保护

const safeHandler = {
  set(target, key, value) {
    const likeKey=Object.keys(target).find(k=>k.toLowerCase()===key.toLowerCase());//用来寻找对象属性中和我们定义的属性很相似,只是大小写不同的属性
    if(!(key in target)&& likeKey){
      //如果我们要设置的这个属性不在我们的target里,并且它有相似的key
      throw new Error(`Oops!Looks like we already have a property ${key} but with the case of ${likeKey}`);
    }
    target[key]=value;
  }
};
const safetyProxy = new Proxy({ id: 2 }, safeHandler);
safetyProxy.ID = 5;

Object.keys(obj)
find()

Set

  • ES6提供的新的数据类型,它类似于数组,但是成员的值都是唯一的,没有重复。
  • 不能通过索引值来获取元素

简单用法:

  1. add()添加元素,size获取Set长度
  2. 如果有一个属性是数值5,还有一个是字符串5,在对象中,数值5会被强制转换成字符串5,但是在Set中则是不同的两个属性。
  3. 如果add一个Set里已存在的元素,它会忽略你的操作
const color = new Set();
color.add("red");
color.add("green");
color.add("yellow");
console.log(color);//Set(3) {"red", "green", "yellow"}

//调用构造函数时初始化
const fruits=new Set(['apple','banana']);
console.log(fruits);//Set(2) {"apple", "banana"}
const color = new Set();
color.add("red");
color.add("green");
color.add("yellow");

console.log(color.size); //获取长度

color.add("5");
color.add(5);
console.log(color); //Set(5) {"red", "green", "yellow", "5", 5}

color.add("yellow");
console.log(color); //Set(5) {"red", "green", "yellow", "5", 5}
  1. delete()删除元素,has()检验一个元素是否存在,clear()清楚所有元素
const color = new Set();
color.add("red");
color.add("green");
color.add("yellow");
color.delete('red');
console.log(color);//Set(2) {"green", "yellow"}

console.log(color.has('red'));//false
console.log(color.has('green'));//true

color.clear();
console.log(color);//Set(0) {}
  1. Set可以遍历
const color = new Set();
color.add("red");
color.add("green");
color.add("yellow");
console.log(color.values());
const iterator=color.values();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

//因为Set部署了遍历器接口,因此可以用for of循环
for(let Color of color){
  console.log(Color);
}

color.forEach((item,key,ownSet) => {
  console.log(item,key,ownSet);
});

数组去重

const nums = [1, 2, 3, 4, 5, 5, 4];
const numSet = new Set(nums);
console.log(numSet);//Set(5) {1, 2, 3, 4, 5}

//将Set转换为数组
const numArr=[...numSet];
console.log(numArr);//[1, 2, 3, 4, 5]

WeakSet

  1. 与Set类似,也是不重复的值的集合
  2. 里面的元素只能是对象,没有size属性
let Raymond={name:'Raymond',age:30};
let Fred={name:'Fred',age:30};

const people=new WeakSet([Raymond,Fred]);
people.add('Alex');//Uncaught TypeError: Invalid value used in weak set
  1. 不能使用for of来循环
let Raymond = { name: "Raymond", age: 30 };
let Fred = { name: "Fred", age: 30 };

const people = new WeakSet([Raymond, Fred]);

for (let person of people) {
  console.log(person);
}
//Uncaught TypeError: people is not iterable

因为WeakSet没有配置iterator(迭代器/遍历器)

  1. 没有forEach方法,相当于不能循环
  2. 没有clear方法
let Raymond = { name: "Raymond", age: 30 };
let Fred = { name: "Fred", age: 30 };

const people = new WeakSet([Raymond, Fred]);

people.forEach(item => console.log(item));
//Uncaught TypeError: people.forEach is not a function
  1. 有自动清理机制
let Raymond = { name: "Raymond", age: 30 };
let Fred = { name: "Fred", age: 30 };

// const people = new WeakSet([Raymond, Fred]);

const peopleArr=[Raymond,Fred];
console.log(peopleArr);
Fred=null;//把Fred对象删除掉
console.log(peopleArr);//Fred仍然存在,这是常说的内存泄漏

//由于peopleArr里还存在对Fred的引用,因此Fred没有被删除

而WeakSet不同,会帮我们把引用删除

let Raymond = { name: "Raymond", age: 30 };
let Fred = { name: "Fred", age: 30 };

let people = new WeakSet([Raymond, Fred]);

console.log(people);
Fred=null;//把Fred对象删除掉
console.log(people);//虽然此时Fred还在,但是在控制台中输入people再次查看,Fred没有了

Map

  • 如果把Set类比为数组,那Map就能类比为对象
  • Map和Set有许多相似的地方,只不过Map存储的是键值对

简单用法:

  1. set()添加元素,size获取键值对数量
const people=new Map();
people.set('Raymond',20);//people.set(key,value)
people.set('Fred',30);
people.set('Greyson',21);
console.log(people.size);//3

//调用构造函数时初始化
const fruits=new Map([['apple',6],['banana',5]]);
console.log(fruits);
  1. Map与对象的不同就是,它的key可以是任意类型的数据
const people=new Map();
people.set({},3)
console.log(people);
  1. 如果想获取里面的属性值,可用get()
const people = new Map();
people.set("Raymond", 20);
people.set("Fred", 30);
people.set("Greyson", 21);
console.log(people.get("Raymond")); //20
  1. delete()删除元素,has()查看某个属性是否存在,clear()清楚所有元素
const people = new Map();
people.set("Raymond", 20);
people.set("Fred", 30);
people.set("Greyson", 21);

people.delete('Raymond');

console.log(people.has("Raymond")); //false
console.log(people.has("Fred")); //true

people.clear();
console.log(people);//Map(0) {}
  1. Map可以遍历
const people = new Map();
people.set("Raymond", 20);
people.set("Fred", 30);
people.set("Greyson", 21);

for (person of people) {
  console.log(person);
}
//可以对person进解构
for (let [key,value] of people) {
  console.log(key,value);
}

people.forEach(function(value, key, map) {
  console.log(value, key, map);
});

简单应用

  • 当我们想存储关于这个对象的信息,但又不存储在对象上
    例子:我想记录页面上每个button被点击的次数
    **方法一:**我们可以给每个button设置一个id,用一个对象来记录它们,但是如果这个button的id被移除了或者相应的对象被移除了,就找不到它点击的信息了

**方法二:**通过原数据来存储button相对应的信息

<button>Hellobutton>
<button>Hibutton>
<button>Worldbutton>
const clickCounts=new Map();
const buttons=document.querySelectorAll('button');
buttons.forEach(button=>{
  clickCounts.set(button,0);
  button.addEventListener('click',function(){
    const val=clickCounts.get(this);
    clickCounts.set(this,val+1);
    
    console.log(clickCounts);
  })
})

WeakMap

  1. 在Map的基础上有一些限制
  2. key只能是对象,没有size属性
let Raymond = { name: "Raymond" };//对象作为key
let Fred = { name: "Fred" };
const strong = new Map();
const weak = new WeakMap();

strong.set(Raymond,'Hi,Raymond');
weak.set(Fred,'Hi,Fred');

console.log(strong);
console.log(weak);
console.log(strong.size);//1
console.log(weak.size)//undefined
let Raymond = { name: "Raymond" }; //对象作为key
let Fred = { name: "Fred" };
const strong = new Map();
const weak = new WeakMap();

strong.set(Raymond, "Hi,Raymond");
weak.set(Fred, "Hi,Fred");

weak.set("Gray", 20);//Uncaught TypeError: Invalid value used as weak map key
  1. 不能循环
let Raymond = { name: "Raymond" };//对象作为key
let Fred = { name: "Fred" };
const strong = new Map();
const weak = new WeakMap();

strong.set(Raymond,'Hi,Raymond');
weak.set(Fred,'Hi,Fred');

for(let person of weak){
  console.log(person);//Uncaught TypeError: weak is not iterable
}

因为WeakMap没有配置iterator(迭代器/遍历器)

  1. 没有clear方法
  2. 有自动清理机制,如果它里面的元素在其他地方没有被引用,垃圾清理机制就会自动清理掉这个元素
let Raymond = { name: "Raymond" }; //对象作为key
let Fred = { name: "Fred" };
const strong = new Map();
const weak = new WeakMap();

strong.set(Raymond, "Hi,Raymond");
weak.set(Fred, "Hi,Fred");

Raymond = null;
Fred = null;

在控制台输入

console.log(strong);
console.log(weak);

可见strong里还有Raymond,weak里什么都没有

你可能感兴趣的:(学习汇总)