参考书籍: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
- 在向服务器发起请求时,经常会有这样的需求,就是第一个请求成功后再发起第二个请求,第二个成功后再发起第三个,而为了实现,通常会把第二个请求放在第一个请求的回调函数中,第三个请求则放在第二个请求的回调函数中。
- 弊端:这样写,随着相关的依赖越来越多,代码嵌套程度越来越深,代码越来越难看,这样很可能会陷入回调地狱(函数作为参数层层嵌套)中。
其他资料: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 给我们提供了一个模块机制。
- 一个模块可以拥有很多变量/函数/类
- 可以通过 export 命令导出它们
- 可以在其他文件中通过 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";
注意
- 一个模块中只能有一个默认导出
- 使用默认导出的话,在导入时可以随意命名
- 使用命名导出的话,在导入时必须使用导出时的名字,且要用大括号引入
app.js
//导入其他包的方法或变量
import { uniq } from "lodash"; //引用lodash里面的uniq方法,这种是命名导出
import moment from "moment"; //导入默认的信息
index.html
<body>
<h1>Hello,world</h1>
<script src="./app.js"></script>
</body>
注意:
- 目前浏览器还在完善 ES6 模块的定义,因此目前不能直接在浏览器中使用它,而是需要通过 webpack、gulp 这样一些打包工具来处理。
- 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", "hi@qq.com");
const Raymond = new User("Raymond", "hi2@qq.com");
//实例化之后重写原型对象属性
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 语法
- 类的声明
class User {
//这个类的内容
}
- 类的表达式
const User = class {
//这个类的内容
};
- 类是特殊的函数,但不会提升,不能在类的定义之前调用
- ES6 的 class 中,两个方法之间不能添加逗号分隔
- 类的静态方法(static 方法)只能通过类名调用,不能通过实例调用
- 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", "hi@qq.com");
const Raymond = new User("Raymond", "hi2@qq.com");
Alfred.githubName = "Niccce";
// Alfred.description();//Alfred.description is not a function
User.description();
console.log(Alfred);
console.log(Alfred.github);
- 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", "hi@qq.com");
Alfred.info();
- 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);
- 在子类上定义的方法如果跟基类上的方法同名的话,会覆盖基类上的方法
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(遍历器/迭代器)
遍历器是一种接口,为各种不同的数据结构提供统一的访问机制。
- 任何数据结构,只要部署了 Iterator 接口,就可以完成遍历操作。
- Iterator 作用:
- 为各种数据结构提供统一的、简便的访问接口;
- 使得数据结构的成员能够按某次序排列;
- ES6 创造了一种新的遍历命令——for of 循环,Iterator 接口主要供 for of 消费。
- 遍历器是一个对象,其中有 next 方法,每次调用 next 方法都会返回数据结构的当前成员信息(value 和 done 两个属性对象),其中 value 属性是当前成员的值,done 属性是一个布尔值,表示变量是否结束。
- ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就认为是“可遍历的”。调用 Symbol.iterator 方法,可以得到当前数据结构默认的遍历器生成函数。
- 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 函数可以开始暂停开始暂停,也可以在调用当中传入另外的参数。
简单语法
- function 命令与函数名之间有一个
*
- 函数体内部使用
yield
语句定义不同的内部状态(‘yield’意思是产出) - Generator 函数在调用后并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器。
- 必须调用遍历器的 next 方法,使得指针移向下一个状态。
- 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应用
- 用于控制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
- Proxy能够帮我们重写对象上默认的方法,用于修改某些操作的默认行为
- proxy原意为代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”
- 语法:
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应用例子
- 格式化电话号码
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()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
- 对用户的操作进行处理或保护
比如给用户提供了一个对象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;
Set
- ES6提供的新的数据类型,它类似于数组,但是成员的值都是唯一的,没有重复。
- 不能通过索引值来获取元素
简单用法:
- 用
add()
添加元素,size
获取Set长度 - 如果有一个属性是数值5,还有一个是字符串5,在对象中,数值5会被强制转换成字符串5,但是在Set中则是不同的两个属性。
- 如果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}
- 用
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) {}
- 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
- 与Set类似,也是不重复的值的集合
- 里面的元素只能是对象,没有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
- 不能使用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(迭代器/遍历器)
- 没有forEach方法,相当于不能循环
- 没有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
- 有自动清理机制
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存储的是键值对
简单用法:
- 用
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);
- Map与对象的不同就是,它的key可以是任意类型的数据
const people=new Map();
people.set({},3)
console.log(people);
- 如果想获取里面的属性值,可用
get()
const people = new Map();
people.set("Raymond", 20);
people.set("Fred", 30);
people.set("Greyson", 21);
console.log(people.get("Raymond")); //20
- 用
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) {}
- 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>Hello</button>
<button>Hi</button>
<button>World</button>
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
- 在Map的基础上有一些限制
- 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
- 不能循环
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(迭代器/遍历器)
- 没有clear方法
- 有自动清理机制,如果它里面的元素在其他地方没有被引用,垃圾清理机制就会自动清理掉这个元素
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里什么都没有