参考书籍:http://es6.ruanyifeng.com/
参考视频:https://www.bilibili.com/video/av47304735
var、let、const
var
- 可以重复声明、可以重新赋值
- 在函数中声明变量,则只能在函数中使用(var 声明的变量的作用域属于 Function Scope)
- 用 var 在 if 语句的代码块中声明,因为没有函数,所以在 if 语句的代码块外也能访问
1 | function hi() { |
- 存在变量提升,声明的语句会被提升到作用域最上方,赋值语句不被提升,留在原地
1 | console.log(i); |
注意:
1 | for (var i = 0; i < 5; i++) { |
- 解决方法就是改用 let
let
- 不可以重复声明、可以重新赋值
用 let 声明变量,只在代码块内可访问(let 声明的变量的作用域属于 block scope)
1
2
3
4
5
6
7
8let count = 10;
let discount = 0.9;
const key = "abc";
if (count > 5) {
let discount = 0.5;
console.log(discount); //0.5
}
console.log(discount); //0.91
2
3let count = 10;
let count = 1;
// Uncaught SyntaxError: Identifier 'count' has already been declared存在变量提升,但因为存在作用域死区(Temporal dead zone),但是若想在当前块级作用域中,这个变量声明之前引用这个变量,就会报错。
- 即在这个作用域开始直到这个变量声明之前,这个变量都处在临时性死区当中,这时引用它会报 ReferenceError
const
- 不可以重复声明、不可以重新赋值
- 用 const 声明时必须立刻赋值
用 const 声明变量,只在代码块内可访问(属于 block scope)
1
2const key = "123";
key = "456"; //delete.html:19 Uncaught TypeError: Assignment to constant variable.1
const key;//Uncaught SyntaxError: Missing initializer in const declaration
1
2
3
4
5if (true) {
const key = "123";
console.log(key); //123
}
console.log(key); //delete.html:22 Uncaught ReferenceError: key is not defined
注意:这样也是不可以的
1 | let i = 0; |
若用 const 声明时,给它的是一个对象,那么,对象的属性仍能改变,因为 const 指向的是对象的引用,这个引用不能变
1
2
3
4
5
6
7
8const person = {
name: "Alred",
age: 30
};
person = {
name: "Alfred"
};
//delete.html:22 Uncaught TypeError: Assignment to constant variable.1
2
3
4
5
6const person = {
name: "Alred",
age: 30
};
person.age = 20;
console.log(person); //正常输出且不报错,其中age为20
- 也可以这样赋值
1 | let abc = { |
- 若希望对象的属性值也不修改的话,可以使用 Object.freeze()方法
1 | const person = { |
- 存在变量提升,也存在作用域死区(和 let 一样)
三者如何选择
- 默认使用 const
- 当变量需要重新绑定或更新的时候使用 let
- 在 ES6 中尽量不适用 var
箭头函数
语法简明
没有参数
1
2
3() => { ... } // 没有参数
x => { ... } // 一个参数,括号可加可不加(x)=>{...}
(x, y) => { ... } // 多个参数1
2
3
4
5
6
7
8
9
10const numbers = [1, 2, 3, 4, 5];
const double = numbers.map(function(number) {
return number * 2;
});
const double2 = numbers.map(number => {
return number * 2;
});
console.log(double); // [2, 4, 6, 8, 10]
console.log(double2); // [2, 4, 6, 8, 10]
可以隐式返回
1 | x => { |
1 | const numbers = [1, 2, 3, 4, 5]; |
this
- 箭头函数没有自己的 this 值,它的 this 是继承它的父作用域的(即上层作用域)
- 箭头函数在指定的时候 this 已经确定,一般情况下不会因为调用而改变,不过会随上层作用域的 this 的变化而变化
- 先看一个例子
1 | const Alfred = { |
调用 Alfred 中的 printHobbies(),第一个 this 指向 Alfred,然后 printHobbies 里的那个函数,是 map 方法的一个回调函数,这个方法是独立运行的,因此这个方法指向的是 window
- 解决方法
1 | const Alfred = { |
或者,使用箭头函数
1 | const Alfred = { |
- 另外:由于箭头函数是匿名函数,所以一般会把函数赋值给一个变量
1 | const greet = name => { |
箭头函数的不适用场景
- 不能作为构造函数,不能用于定义对象方法
1 | // const person = (name, points) => { //Uncaught TypeError: person is not a constructor |
真的需要用到 this 的时候不能使用箭头函数
1
2
3
4<div
id="app"
style="width: 50px ; height: 50px; background-color: #000000;"
></div>1
2
3
4
5
6
7
8
9
10
11const app = document.getElementById("app");
// app.addEventListener('click', () => {
app.addEventListener("click", function() {
//此处的方法需要绑定到app上,但是用箭头函数是不会进行绑定的
console.log(this); //Window
this.style.height = "100px";
setTimeout(() => {
//此处希望this仍然指向app,使用箭头函数可以使this与外层保持一致
this.style.height = "50px";
}, 500);
});需要使用 arguments 对象时不能使用箭头函数
1 | // sum函数返回所有参数的和 |
函数参数的默认值
1 | function multiply(a, b) { |
用 ES6 可以简化为
1 | function multiply(a = 5, b = 3) { |
- 如果有传参,则使用传进来的值,如果没有传值,函数默认传的是 undefined,if(typeof a === undefined),就会使用默认值
ES6 模版字符串
基础用法
- 用反引号”`”来定义字符串
- 用
${}
来引用变量,也可以放表达式、对象的属性、函数 - 直接使用反引号可以保存 html 代码的层级结构,不需要用\或数组去模拟
- 模版字符串可以嵌套使用
1 | const person = "Alfred"; |
简单应用:
1 | const Alfred = { |
根据以上例子,还可以将读取对象内容的代码封装成函数
1 | function renderTodos(todos) { |
标签模版
- 即在模版字符串中添加标签,根据我们自定义的规则,返回我们想要的字符串
- 标签对应 js 里的一个函数名
- 使用标签模版字符串时,最后那个字符串返回的内容是由标签(函数)决定的
- 这个函数可以接受模版字符串作为参数,第一个参数为模版字符串里的普通字符,其后的参数是模版字符串里的变量
1 | //参数可以用逗号分隔一个一个写 |
以上代码优化、简化:
1 | function hightlight(strings, ...values) { |
注意:
- 如果模版字符串是以变量开头或结尾的话,打印参数 strings,会看到里面有空字符串
forEach():https://www.runoob.com/jsref/jsref-foreach.html
reduce():https://www.runoob.com/jsref/jsref-reduce.html
最佳实践——过滤用户输入(防 xss 攻击)
更多关于标签模版的内容:https://www.cnblogs.com/sminocence/p/6832331.html
对象解构
- 简单用法
1 | const Alfred = { |
- 如果还是想提前声明变量,可以使用大括号,见下方
1 | const Alfred = { |
- 对象解构可以嵌套
1 | const Alfred = { |
- 如果提前使用了一个同名变量,就要重命名
1 | const Alfred = { |
- 如果要在对象中拿一个没有的变量,会返回 undefined
1 | const Alfred = { |
通常,如果没有这个值,可以给定一个默认值,只要对象里没有这个值(即等于 undefined),或对象里该值就是 undefined 时,才使用这个默认值,否则都不会使用(0,false,null 也不使用默认值)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const Alfred = {
name: "Alfred",
age: 30,
family: {
mother: "one",
father: "two",
brother: "three"
}
};
const father = "Dad";
const {
father: F,
mother,
brother: b,
sister = "have no sister"
} = Alfred.family;
console.log(sister); //have no sister这种使用默认值的场景在一些第三方库中非常常用
数组解构
- 简单用法
1 | const numbers = ["one", "two", "three", "four"]; |
- 数组解构指定默认值
1 | const details = ["Alfred", null]; |
- 常用场景
- 交换变量的值
1 | let a = 10; |
for of
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols
- for of 循环可用于遍历可迭代对象
- 可迭代对象:部署了
iterator
(迭代器/遍历器)的接口,或定义了Symbol.iterator
方法的数据结构 - 遍历器可用于定义数据结构的遍历方式,js 给很多内置的数据结构提供了内置的遍历器接口
- for of 循环不支持遍历对象
1 | Array.prototype.sayhi = function() { |
- 定义一个数组 fruits,在控制台输入
fruits.entries();
,可看到它的遍历器接口
1 | const fruits = ["apple", "banana", "orange", "pear"]; |
因此可以改写成
1 | const fruits = ["apple", "banana", "orange", "pear"]; |
- 用于其他数据结构的一些场景
遍历 arguments 对象来计算数组的和(注意:arguments 对象的原型是 Object)
1
2
3
4
5
6
7
8
9function sum() {
let total = 0;
for (let num of arguments) {
total += num;
}
console.log(total);
return total;
}
sum(1, 2, 3, 4, 5);应用于字符串
1
2
3
4let name = "Alfred";
for (let char of name) {
console.log(char);
}用于获取 NodeList,我们经常需要获取一些 dom 集合,用于操作里面的元素
1
2
3
4
5
6
7
8
9<div>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
</ul>
</div>1
2
3.completed {
text-decoration: line-through;
}1
2
3
4
5
6
7const list = document.querySelectorAll("li");
// console.log(list);
for (let li of list) {
li.addEventListener("click", function() {
this.classList.toggle("completed");
});
}
ES6 新增的字符串函数
.startsWith()
- 是否(从第 n 位开始)以’xx’字符串开头
- 区分大小写
1 | const num = "881208Alfred"; |
.endsWith()
- 是否以’xx’字符串结尾
- 使用与 startsWith 相似
- 区分大小写
1
2
3
4
5console.log(num.endsWith("ed")); //true,是否以'es'结尾
console.log(num.endsWith("1208", 6)); //true,注意6是1208中8的下标后一位
console.log(num.endsWith("A", 7)); //true
console.log(num.endsWith("a", 7)); //false
.includes()
- ‘xx’字符串是否包含在原字符串里
1
2
3
4
5const sentence = "Alfred loves his father";
console.log(sentence.indexOf("is") !== -1); //ES5,is字符串存在在sentence内
console.log(sentence.includes("is")); //ES6,true
console.log(sentence.includes("is", 14)); //true,从下标为14位及之后是否存在is
console.log(sentence.includes("is", 15)); //false
.repeat()
- 让某字符串重复 n 次
1
2
3
4
5
6
7
8
9
10
11const sentence = "Alfred loves his father";
const sentence2 = "I love programing";
const title = `${"哈".repeat(3)} ${sentence} ${"喽".repeat(3)}`;
console.log(title); //哈哈哈 Alfred loves his father 喽喽喽
//字符串右对齐
function padder(string, length = 25) {
return `${" ".repeat(Math.max(length - string.length, 0))}${string}`;
}
console.log(padder(sentence));
console.log(padder(sentence2));
ES6 为数组提供的新方法
Array.from()
Array.from()
用于把一个类数组对象/可遍历对象转换为一个数组- 类数组对象是拥有 length 属性的对象
- 可遍历对象即可迭代对象(for of 处有相关描述)
- from()不是数组原型上的方法
1 | const num = []; |
简单运用:
1 | <ul> |
1 | const todos = document.getElementsByTagName("li"); |
要改成
1 | const todos = document.getElementsByTagName("li"); |
- Array.from()可传入第二个参数,是一个函数,相当于数组的 map 方法,会对转化成数组的每个元素执行相对应的函数
1 | const todos = document.getElementsByTagName("li"); |
- 可将函数里的 arguments 这个类数组对象转换为数组
1 | function sum() { |
改成
1 | function sum() { |
Array.of()
Array.of()
用于弥补 Array 这个构造函数的不足
1 | console.log(new Array(1)); //length为1的空数组 |
- of()不是数组原型上的方法
1 | const num = []; |
ES6 中数组的新方法
.find()
- 用于寻找数组里的某个满足条件的元素
- 接受一个函数作为参数
- 函数可接受三个参数,后两个为可选
inventory.find(element,index,array)
- 函数可接受三个参数,后两个为可选
- 当找到一个符合要求的元素就立刻返回
1 | const inventory = [ |
简化为:
1 | const inventory = [ |
.findIndex()
- 找到数组中某个元素的索引
- 接受一个函数作为参数
- 函数可接受三个参数,后两个为可选
inventory.findIndex(element,index,array)
- 函数可接受三个参数,后两个为可选
- 返回第一个找到的元素的索引
1 | const inventory = [ |
.some()
- 接受一个函数作为参数,返回布尔值
- 若数组中某个元素满足了测试函数,就返回 true 并且停止执行
1 | const inventory = [ |
.every()
- 接受一个函数作为参数,返回布尔值
- 只有当所有元素都满足测试函数,才会返回 true,否则,遇到第一个条件是 false 的元素就会立即返回并停止执行
1 | const inventory = [ |
剩余参数
- 剩余参数只能用于参数的最后一个,不能是中间
- 还可用于解构
直接看一些例子来理解用法吧
1 | function sum(...numbers) { |
1 | function converCurrency(rate, ...amounts) { |
扩展运算符
- 剩余参数是把很多参数整合成数组,而扩展运算符则是把一个可遍历对象的每个元素扩展为一个新的参数序列。
1 | console.log([..."Alfred"]); //["A", "l", "f", "r", "e", "d"] |
- 来看一个例子:想在把两个数组连接起来,并在两个数组之间添加一个 Raymond
1 | const actor = ["Jhon", "Thomas"]; |
- 再来看一种情况
1 | const actor = ["Jhon", "Thomas"]; |
ES5 中常见解决方法:
1 | const actor = ["Jhon", "Thomas"]; |
运用扩展运算符解决:
1 | const actor = ["Jhon", "Thomas"]; |
其他运用场景
- 代替Array.from>)
1 | //因document.getElementsByTagName("li")的原型不是数组,因此没有map方法 |
- 扩展对象的属性
1 | const favorites = { |
- 数组中删除对象元素
1 | //想要删除数组中id为2的对象 |
- 在函数中的应用
1 | //将singers的元素push到actor中 |
- 减轻我们一个一个传入参数的痛苦
1 | const dateFields = [2019, 9, 1, 20, 20]; |
对象字面量的改进
- 简化书写
- 当对象内的属性名和对象外的变量名一样时,对象内可简化书写
1 | const name = "Raymond"; |
- 在对象内定义方法时可以简写
1 | //ES5 |
- ES6 提供了计算属性
1 | let id = 0; |
1 | const keys = ["name", "age", "birthday"]; |
Promise
- 在向服务器发起请求时,经常会有这样的需求,就是第一个请求成功后再发起第二个请求,第二个成功后再发起第三个,而为了实现,通常会把第二个请求放在第一个请求的回调函数中,第三个请求则放在第二个请求的回调函数中。
- 弊端:这样写,随着相关的依赖越来越多,代码嵌套程度越来越深,代码越来越难看,这样很可能会陷入回调地狱(函数作为参数层层嵌套)中。
axios(promise 库)
1 | let userName; |
- them 相当于一个监听
- catch 用于监听错误的发生,用了 catch,发生错误时会告诉你发生了错误,没用,则会反馈说 Uncaught Error
编写一个简单的 promise
- Promise 构造函数的参数是一个函数,这个函数有两个内置参数 resolve,reject
1 | const p = new Promise((resolve, reject) => { |
1 | const p = new Promise((resolve, reject) => { |
处理多个 Promise
- .all 方法,只有传入的所有 promise 结果都是 resolve,才会触发 then,只要有一个 promise 的结果是 reject,就会执行 catch
1 | const usersPromise = new Promise((resolve, reject) => { |
- .race 方法的状态由第一个返回的 promise 决定,第一个返回的 promise 结果是什么,那么整个 Promise.race 的结果就是什么,第一个返回的 promise 是 resolve,就执行.then,否则执行 catch
1 | const usersPromise = new Promise((resolve, reject) => { |
1 | const usersPromise = new Promise((resolve, reject) => { |
Symbol
来看一下这样一种情景
1 | const classRoom = { |
- ES6 中的新数据类型
- Symbol 用于生成唯一的标志符来避免命名冲突,每个 symbol 都是不一样的
- typeof 可判断一个变量是不是 Symbol 类型
1 | const greyson = Symbol(); |
- 为了更好的调试,可以在定义 symbol 时添加描述
1 | const greyson = Symbol("greyson"); |
因此,上方的情景可以改写为
1 | const classRoom = { |
- 用 Symbol 定义的属性不能遍历,因此可以用它作为私有属性,在对象内部使用
- 可用
Object.getOwnPropertySymbols()
来访问
1 | const classRoom = { |
- 注意:获取属性值处必须使用
[]
,如果使用.
访问,sym 代表的 Symbol 会被当成字符串,就等同于使用了classRoom['sym']
,最终结果返回的值都是 undefined
模块(Modules)
在过去,我们要把代码模块化的时候,可能需要很多 script 标签,如果引用很多模块的话,一方面会影响访问的速度,另一方面还会定义很多全局变量,可能会导致命名冲突
1 | <body> |
为了结局这一问题 ES6 给我们提供了一个模块机制。
- 一个模块可以拥有很多变量/函数/类
- 可以通过 export 命令导出它们
- 可以在其他文件中通过 import 命令来引用它们,然后在自己的代码当中使用
a.js
1 | //默认导出 |
1 | //命名导出 |
或
1 | const apiKey = "abc123"; |
b.js
1 | //使用默认导出时 |
1 | import { apiKey, age } from "./a.js"; |
当引入的变量名和模块里已有的变量名冲突的话,可以使用 as 重命名
1 | import { Key as apiKey, age } from "./a.js"; |
注意
- 一个模块中只能有一个默认导出
- 使用默认导出的话,在导入时可以随意命名
- 使用命名导出的话,在导入时必须使用导出时的名字,且要用大括号引入
app.js
1 | //导入其他包的方法或变量 |
index.html
1 | <body> |
注意:
- 目前浏览器还在完善 ES6 模块的定义,因此目前不能直接在浏览器中使用它,而是需要通过 webpack、gulp 这样一些打包工具来处理。
- Babel 可以帮我们把 ES6 代码转换成 ES5 代码,以保证一些还没有完善 ES6 的浏览器能够使用
class 继承语法糖
原型继承
1 | function User(name, email) { |
class 语法
- 类的声明
1 | class User { |
- 类的表达式
1 | const User = class { |
- 类是特殊的函数,但不会提升,不能在类的定义之前调用
- ES6 的 class 中,两个方法之间不能添加逗号分隔
- 类的静态方法(static 方法)只能通过类名调用,不能通过实例调用
- class 内可以定义一个属性的 get 和 set 方法
1 | const User = class { |
- class 中的方法名可以使用计算属性的格式
1 | let methodName = "info"; |
- class 都要用 new 来调用,不能直接用类名调用
class 继承
1 | class Animal { |
- 在子类上定义的方法如果跟基类上的方法同名的话,会覆盖基类上的方法
1 | class Animal { |
扩展内建对象 Array
简单例子
1 | class MyArray extends Array { |
简单运用
1 | class singers extends Array { |
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 的值,在打印出来
1 | const colors = ["red", "green", "yellow"]; |
- 在 ES6 中有三种类型的集合对象,分别是 Array、Map、Set,针对这三种数据类型,ES6 为它们提供了内建的遍历器,我们可以通过调用相应的方法来获取它们
1 | //colors.entries()这个方法返回的是元素的属性名可属性值 |
1 | const colors = ["red", "green", "yellow"]; |
1 | //colors.keys()返回的是元素的索引值 |
编写自己的遍历器
1 | Array.prototype.Myvalues = function() { |
Generator(生成器)
目前 JavaScript 的函数都是从上到下依次执行直到结束,而 Generator 函数可以开始暂停开始暂停,也可以在调用当中传入另外的参数。
简单语法
- function 命令与函数名之间有一个
*
- 函数体内部使用
yield
语句定义不同的内部状态(‘yield’意思是产出) - Generator 函数在调用后并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器。
- 必须调用遍历器的 next 方法,使得指针移向下一个状态。
- Generator 函数是分段执行的,yield 语句是暂停执行的标记,而 next 方法可以恢复执行
1 | function* listColors() { |
其他例子
1 | function* num(){ |
1 | const singers = [ |
Generator应用
- 用于控制Ajax工作流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<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
1 | const person={name:'Alfred',age:30}; |
Proxy应用例子
格式化电话号码
1
2
3
4
5
6
7
8
9
10
11
12const 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-3333match()方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
join()方法用于把数组中的所有元素通过指定的分隔符进行分隔,然后放入一个字符串。
replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。对用户的操作进行处理或保护
比如给用户提供了一个对象person,里面有一个id属性,有时候别人可能不会很清楚你的’id’的大小写1
2
3
4const person = { id: 2 };
person.ID = 2;
person.iD = 2;
person.id = 2;为了避免用户花费很多时间来排除这样一些bug,我们可以通过proxy来对用户的操作进行处理或保护
1
2
3
4
5
6
7
8
9
10
11
12const 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里已存在的元素,它会忽略你的操作
1 | const color = new Set(); |
1 | const color = new Set(); |
- 用
delete()
删除元素,has()
检验一个元素是否存在,clear()
清楚所有元素
1 | const color = new Set(); |
- Set可以遍历
1 | const color = new Set(); |
数组去重1
2
3
4
5
6
7const 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属性
1
2
3
4
5let 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来循环
1
2
3
4
5
6
7
8
9let 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方法
1
2
3
4
5
6
7let 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
2
3
4
5
6
7
8
9
10
11let 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不同,会帮我们把引用删除
1
2
3
4
5
6
7
8let 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
获取键值对数量1
2
3
4
5
6
7
8
9const 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可以是任意类型的数据
1
2
3const people=new Map();
people.set({},3)
console.log(people);如果想获取里面的属性值,可用
get()
1
2
3
4
5const people = new Map();
people.set("Raymond", 20);
people.set("Fred", 30);
people.set("Greyson", 21);
console.log(people.get("Raymond")); //20用
delete()
删除元素,has()
查看某个属性是否存在,clear()
清楚所有元素1
2
3
4
5
6
7
8
9
10
11
12const 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可以遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const 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相对应的信息1
2
3<button>Hello</button>
<button>Hi</button>
<button>World</button>
1 | const clickCounts=new Map(); |
WeakMap
- 在Map的基础上有一些限制
key只能是对象,没有size属性
1
2
3
4
5
6
7
8
9
10
11
12let 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)//undefined1
2
3
4
5
6
7
8
9let 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
2
3
4
5
6
7
8
9
10
11let 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方法
有自动清理机制,如果它里面的元素在其他地方没有被引用,垃圾清理机制就会自动清理掉这个元素
1
2
3
4
5
6
7
8
9
10let 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;在控制台输入
1
2console.log(strong);
console.log(weak);可见strong里还有Raymond,weak里什么都没有
- 本文作者: Niccce
- 本文链接: https://niccce.github.io/2019/09/04/Knowledge-Points-of-ES6/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!