ES6--ES11新特性
ES6-ES11新特性
ES6官方文档:
参考笔记:https://docs.mphy.top/#/ECMAScript6+/ch01
一、ES6相关介绍
ES全程EcmaScript,是脚本语言的规范,而平时经常编写的JavaScript,是EcmaScript的一种实现,所以ES新特性其实指的就算JavaScript的新特性。
1.1什么是ECMA
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该组织改名为Ecma 国际。
1.2什么是ECMAScript
ECMAScript 是由 Ecma 国际通过ECMA-262 标准化的脚本程序设计语言。
1.3什么是 ECMA-262
Ecma 国际制定了许多标准,而ECMA-262 只是其中的一个,所有标准列表查看:http://www.ecma-international.org/publications/standards/Standard.htm
ECMA-262 历史版本查看网址: http://www.ecma-international.org/publications/standards/Ecma-262-arch.htm
- ES5 是 ECMAScript 第5版,2009年发布。
- ES6 是 ECMAScript 第6版,2015年发布,也叫 ES2015。
- 从 ES6 开始,每年发布一个版本,版本号比年份最后一位大 1。
1.4 谁在维护 ECMA-262
TC39(Technical Committee 39)是推进ECMAScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。
1.5 为什么要学ES6
- ES6 的版本变动内容最多,具有里程碑意义
- ES6 加入许多新的语法特性,编程实现更简单、高效
- ES6 是前端发展趋势,就业必备技能
1.6 ES6兼容性
二、ES6新特性
1. let关键字
let
关键字用来声明变量,使用 let
声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
let a;
let b,c,d;
let e = 100;
let f=521,g='iloveyou',h=[];
//1.变量不能重复声明 (会报错)
//而 var 声明变量时,可以重复声明 不报错。
// let star='罗志祥';
// let star='小猪';
//2.块儿级作用域 全局,函数,eval
{
let girl = '小红';
console.log(girl);
}
//外面读取不到
// console.log(girl);
//3.不存在变量提升
//出现undefined 而不报错
console.log(song);
//会报错 Uncaught ReferenceError: Cannot access 'song2' before initialization
console.log(song2);
var song = '恋爱达人';
let song2 = '恋爱达人';
//4.不影响作用域链
{
let school = '清华大学';
function fn(){
console.log(school);
}
fn();
}
</script>
</body>
</html>
应用场景:以后声明变量使用let 就对了
案例1:给多个 div
循环注册点击事件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.item{
width: 80px;
height: 60px;
border: 1px blue solid;
margin-top: 10px;
/* background-color: aqua; */
}
</style>
</head>
<body>
<div class="container">
<h2 class="page-header">点击切换颜色</h2>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
<script>
//获取div元素对象
let items = document.getElementsByClassName('item');
//遍历并绑定事件
//var i=0;i<items.length;i++ 就不行,需要讲var改为let
for(let i=0;i<items.length;i++){
items[i].onclick = function(){
//修改当前元素的背景颜色
// this.style.background = 'pink';
items[i].style.background = 'pink';
}
}
</script>
</body>
</html>
2. const 关键字
const
关键字用来声明常量,const
声明有以下特点:
- 声明必须赋初始值
- 标识符一般为大写
- 不允许重复声明
- 值不允许修改
- 块级作用域
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//声明常量
const SCHOOL = '清华大学';
//1.一定要赋初始值
//要不然会报错
//Missing itializer in const declaration (at new_file3.html:13:10)
// const A;
//2.一般常量使用大写(潜规则)
const a = 100; //小写也不会报错
console.log(a);
//3.常量的值不能修改
//Assignment to constant variable.
//以下会报错
// SCHOOL = 'QingHua';
//4.块级作用域
{
const PLAYER = 'uzi';
console.log(PLAYER);
}
// console.log(PLAYER); //这样会报错
//5.对于数组和对象的元素修改,不算做对常量的修改,不会报错
const TEAN = ['UZI','MXLG','Ming'];
TEAN.push('Meiko'); //这样不会报错
TEAN = 100; //这样会报语法错误
</script>
</body>
</html>
3.变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为 解构赋值。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//1.数组的解构
const F4 = ['小沈阳','刘能','赵四'];
let [xiao,liu,zhao] = F4;
console.log(xiao);
console.log(liu);
console.log(zhao);
//2.对象的解构
const zhao = {
name:'赵本山',
age:'不祥',
xiaopin: function(){
console.log("我可以演小品");
}
};
let {name , age ,xiaopin} = zhao;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();
//讲xiaopin 解构出来,为了让其书写方便,减少冗余。
// let {xiaopin} = zhao;
// xiaopin();
//不解构 需要这样写
zhao.xiaopin();
</script>
</body>
</html>
4.模板字符串
模板字符串(template string)是增强版的字符串,用反引号 ` 标识,特点:
- 字符串中可以出现换行符
- 可以使用
${xxx}
形式输出变量
应用场景:当遇到字符串与变量拼接的情况使用模板字符串。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//es6引入新的声明字符串的方式 [``] '' ""
//1.声明
let str = `我也是一个字符串哦!`;
console.log(str,typeof str);
// 输出结果: 我也是一个字符串哦! string
//2.内容中可以直接出现换行符
let str1 = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>艾伦</li>
</ul>`
//3. 变量拼接
let lovest = '艾伦';
let out = `${lovest} 是我心目中最搞笑的演员!`;
console.log(out);
//变量拼接时 必须使用 ${}
</script>
</body>
</html>
5.对象的简化写法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法
let name ='清华大学';
let change = function(){
console.log('我们可以改变世界!');
}
const school ={
// name:name, //这写法和下面意思一样
name, //这是上面的简写
change,
// improve:function(){} //以下是简化
improve(){
console.log("我们可以提高你的技能");
}
}
console.log(school);
school.improve();
</script>
</body>
</html>
6.箭头函数以及声明特点
ES6 允许使用「箭头」=>
定义函数。
- function 写法:
function fn(param1, param2, …, paramN) {
// 函数体
return expression;
}
- => 写法:
let fn = (param1, param2, …, paramN) => {
// 函数体
return expression;
}
箭头函数的 注意点:
- 如果形参只有一个,则小括号可以省略
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
- 箭头函数
this
始终指向声明时所在作用域下this
的值 - 箭头函数不能作为构造函数实例化
- 不能使用
arguments
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//es6 允许使用【箭头】 ( => ) 定义函数。
//声明一个函数
let fn = function(){
}
//声明箭头函数
let fn1 = (a,b)=>{
return a+b;
}
let result = fn1(1,2);
console.log(result);
//1.this 是静态的 this始终指向函数声明时所在作用域下的this的值
function getName(){
console.log(this.name);
}
let getName2 = ()=>{
console.log(this.name);
}
//设置window 对象的 name属性
window.name = '清华同方';
const school = {
name: "ATGUIGU"
}
//直接调用
getName(); //清华同方
getName2(); //清华同方
//call 方法调用
getName.call(school); //ATGUIGU
getName2.call(school); //清华同方
//2.不能作为构造实例化对象
//以下汇报 person is not a constructor 错误
/* let person = (name,age)=>{
this.name = name;
this.age = age;
}
let me = new person('xiao',30);
console.log(me); */
//3.不能使用 arguments 变量
/* let fn2 =()=>{
console.log(arguments);
}
fn2(1,2,3); */ // arguments is not defined
//4.箭头函数的简写
//1)省略小括号,当形参有且只有一个的时候
let add = n =>{
return n + n;
}
console.log(add(5)); //10
//2)省略花括号,当代码体只有一条语句的时候,此时return必须省略
//而且语句的执行结果就是函数的返回值
// let pow = (n) =>{
// return n*n;
// };
let pow = (n) => n * n;
console.log(pow(6)); //36
</script>
</body>
</html>
7.箭头函数的实践与应用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#ad{
width: 200px;
height: 200px;
background:blue;
}
</style>
</head>
<body>
<div id="ad"></div>
<script>
//案例1 点击 div 2s 后颜色变成 粉色
//获取元素
let ad = document.getElementById('ad');
//绑定事件
ad.addEventListener("click",function(){
//保存this的值
let _this = this;
//定时器
setTimeout(function(){
console.log(_this)
// ad.styl e.background = 'pink';
//_this 代表 ad
_this.style.background='pink'
},2000);
})
</script>
</body>
</html>
8.函数参数的默认值设置
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//ES6 允许给函数参数赋初始值
//1.形参初始值 具有默认值的参数,一般位置要靠后(潜规则)
function add(a,b,c=10){
return a+b+c;
}
// let result = add(1,2,3);//6
let result = add(1,2);//13
console.log(result);
//2.与解构赋值结合
function connect({host='127.0.0.1',username,password}){
console.log(host); //localhost
console.log(username); //root
console.log(password); //root
}
connect({
host:'localhost',
username:'root',
password:'root'
})
</script>
</body>
</html>
9.rest参数
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//es6引入rest 参数,用于获取函数的实参,用来代替 arguments
//es5获取实参的方式
function date(){
console.log(arguments);
}
date('百汇','想念','思慧');
//运行结果 Arguments(3) ['百汇', '想念', '思慧']
//rest 参数
function date1(...args){
console.log(args);
}
date1('悟空','八戒','沙僧');
// ['悟空', '八戒', '沙僧'] 这样运行结果为数组
//rest 参数必须要放到参数最后
function fn(a,b,...args){
console.log(a); // 1
console.log(b); //2
console.log(args); // [3, 4, 5, 6]
}
fn(1,2,3,4,5,6);
//运行结果: 1
// 2
// [3, 4, 5, 6]
</script>
</body>
</html>
10.扩展运算符的介绍
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
// ... 扩展运算符能将 [数组] 转换未逗号分隔的 [参数序列]
//声明一个数组
const tfboys = ['易烊千玺','王源','王俊凯'];
//声明一个函数
function chunwan(){
console.log(arguments);
}
chunwan(...tfboys);
// 输出结果:Arguments(3) ['易烊千玺', '王源', '王俊凯', callee: ƒ, Symbol(Symbol.iterator): ƒ]
chunwan(tfboys);
//Arguments [Array(3), callee: ƒ, Symbol(Symbol.iterator): ƒ]
//0: (3) ['易烊千玺', '王源', '王俊凯']
</script>
</body>
</html>
14.扩展运算符应用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div></div>
<div></div>
<div></div>
<script>
//1.数组的合并
const kuaizi = ['王太利','肖央'];
const fenghaung = ['曾意','玲花'];
const zhixuanzuhe = kuaizi.concat(fenghaung); //es5 中的标准
console.log(zhixuanzuhe); // ['王太利', '肖央', '曾意', '玲花']
const zhuixuanzuhe2 = [...kuaizi,...fenghaung];
console.log(zhixuanzuhe); // ['王太利', '肖央', '曾意', '玲花']
//2.数组的克隆
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua];
console.log(sanyecao); // ['E', 'G', 'M']
//3.讲伪数组转为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divs); //NodeList(3) [div, div, div]
console.log(divArr); //[div, div, div]
</script>
</body>
</html>
11.Symbol
11.1 Symbol的介绍与创建
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
JavaScript 的七种基本数据类型:
- 值类型(基本类型):string、number、boolean、undefined、null、symbol
- 引用数据类型:object(包括了array、function)
Symbol 的特点:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义的对象属性不能使用
for...in
循环遍历,但是可以使用Reflect.ownKeys
来获取对象的所有键名
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//创建Symbol
let s = Symbol();
console.log(s,typeof s); //Symbol() 'symbol'
let s2 = Symbol('清华大学');
let s3 = Symbol('清华大学');
console.log( s2 === s3); //false
let s4 = Symbol.for('清华大学');
let s5 = Symbol.for('清华大学');
console.log( s4 === s5); //true
//不能与其他数据进行运算 (以下这些都会报错)
let result = s + 100;
let result = s > 100;
let result = s + s;
//数据类型
//USONB
//u undefined
//s string symbol
//o object
//n null number
//b boolean
</script>
</body>
</html>
11.2 对象添加Symbol类型的属性
案例:安全的向对象中添加属性和方法。
分析:如果直接向对象中添加属性或方法,则原来对象中可能已经存在了同名属性或方法,会覆盖掉原来的。所以使用 Symbol
生成唯一的属性或方法名,可以更加安全的添加。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
// 这是一个 game 对象,假设我们不知道里面有什么属性和方法
//向对象中添加方法 up down
const game = {
name: '俄罗斯方块',
up: function () { },
down: function () { }
}
//声明一个对象
let methods = {
up:Symbol(),
down:Symbol(),
};
game[methods.up] = function(){
console.log("我可以改变形状");
}
game[methods.down] = function(){
console.log("我可以快速下降");
}
console.log(game);
//{name: '俄罗斯方块', up: ƒ, down: ƒ, Symbol(): ƒ, Symbol(): ƒ}
let youxi = {
name:'狼人杀',
[Symbol('say')]:function(){
console.log("我可以发言");
},
[Symbol('zibao')]:function(){
console.log("我可以自爆");
}
}
console.log(youxi);
//{name: '狼人杀', Symbol(say): ƒ, Symbol(zibao): ƒ}
</script>
</body>
</html>
11.3 Symbol的内置属性
除了定义自己使用的 Symbol 值以外,ES6 还提供了11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
方法 | 描述 |
---|---|
Symbol.hasInstance |
当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable |
对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于Array.prototype.concat() 时,是否可以展开 |
Symbol.species |
创建衍生对象时,会使用该属性 |
Symbol.match |
当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace |
当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.search |
当该对象被 str.search(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.split |
当该对象被 str.split(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.iterator |
对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive |
该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag |
在该对象上面调用 toString() 方法时,返回该方法的返回值 |
Symbol. unscopables |
该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。 |
案例1:Symbol.hasInstance
方法判断是否属于这个对象时被调用。
class A {
static [Symbol.hasInstance]() {
console.log('判断是否属于这个对象时被调用');
}
}
let obj = {};
console.log(obj instanceof A)
// 判断是否属于这个对象时被调用
// false
案例2:数组使用 concat
方法时,是否可以展开。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2));
// [ 1, 2, 3, [ 4, 5, 6, [Symbol(Symbol.isConcatSpreadable)]: false ] ]
console.log(arr1.concat(arr3));
// [ 1, 2, 3, 4, 5, 6 ]
12.迭代器 iterator
12.1定义
遍历器(Iterator
)就是一种机制。它是一种接口,为各种不同的数据结构提 供统一的访问机制。任何数据结构只要部署 Iterator
接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator
接口主要供for...of
消费。 - 原生具备
iterator接口的数据(可用for of 遍历)
Array
Arguments
Set
Map
String
TypedArray
NodeList
案例:使用 next()
方法遍历原生自带 iterator
接口的数据:
//声明一个数组
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
//使用 for... of 遍历数组
for(let v of xiyou){
console.log(v)
}
//唐僧 孙悟空 猪八戒 沙僧
//使用 for...in 遍历数组
for(let v in xiyou){
console.log(v)
}
// 0
// 1
// 2
// 3
// 遍历数组
let xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
let iter2 = xiyou[Symbol.iterator]();
console.log(iter2.next()); // { value: '唐僧', done: false }
console.log(iter2.next()); // { value: '孙悟空', done: false }
console.log(iter2.next()); // { value: '猪八戒', done: false }
console.log(iter2.next()); // { value: '沙僧', done: false }
上面的案例只是为了证明他们自带 iterator
接口,实际上直接使用 for...of
方法遍历即可(iterator
接口为 for...of
)服务。例如,可以使用 for [k, v] of map
来遍历 Map 数据结构中的键和值。
const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);
for (let [k, v] of mp) {
console.log(k, v);
}
/*
a 1
b 2
c 3
*/
12.2工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的
next
方法,指针自动指向数据结构的第一个成员 - 接下来不断调用
next
方法,指针一直往后移动,直到指向最后一个成员 - 每调用
next
方法返回一个包含value
和done
属性的对象
应用场景:需要自定义遍历数据的时候,要想到迭代器。
12.3 自定义遍历数据
我们可以通过给数据结构添加自定义 [Symbol.iterator]()
方法来使该数据结构能够直接被遍历,从而使 for...of
能够直接遍历指定数据,达到为 for...of
服务的功能。
// 需求:遍历对象中的数组
const xiaomi = {
uname: '小明',
course: [ '高数', '大物', '英语', '数据库' ],
// 通过自定义 [Symbol.iterator]() 方法
[Symbol.iterator]() {
// 初始指针对象指向数组第一个
let index = 0;
// 保存 xiaomi 的 this 值
let _this = this;
return {
next: function () {
// 不断调用 next 方法,直到指向最后一个成员
if (index < _this.course.length) {
return { value: _this.course[index++], done: false };
} else {
// 每调用next 方法返回一个包含value 和done 属性的对象
return { value: undefined, done: true };
}
}
}
}
}
// for...of直接遍历达到目的
for (let v of xiaomi) {
console.log(v);
}
//高数
//大物
//英语
//数据库
13.生成器 Generator
13.1生成器函数声明与调用
生成器函数是 ES6 提供的一种 异步编程解决方案,语法行为与传统函数完全不同。成器函数的参数传递
*
的位置没有限制- 使用
function * gen()
和yield
可以声明一个生成器函数。生成器函数返回的结果是迭代器对象,调用迭代器对象的next
方法可以得到yield
语句后的值。 - 每一个
yield
相当于函数的暂停标记,也可以认为是一个分隔符,每调用一次next()
,生成器函数就往下执行一段。 next
方法可以传递实参,作为yield
语句的返回值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//生成器就是一个特殊的函数
//异步编程 纯回调函数 node fs ajax mongdb
//yield 为函数代码的分隔符
function * gen(){
// console.log("hello generator");
// console.log(1111);
yield '一只没有耳朵';
// console.log(222);
yield '一只没有尾巴';
// console.log(333);
yield '真奇怪';
// console.log(444);
}
let iterator = gen();
// console.log(iterator);
// iterator.next(); //hello generator
//遍历
for(let v of gen()){
console.log(v);
}
//一只没有耳朵
// 一只没有尾巴
// 真奇怪
</script>
</body>
</html>
13.2生成器函数的参数传递
function* generator(arg) {
console.log(arg); // 生成器第 1 段
let one = yield 111;
console.log(one); // 生成器第 2 段
let two = yield 222;
console.log(two); // 生成器第 3 段
let three = yield 333;
console.log(three); // 生成器第 4 段
}
let iter = generator('aaa'); // 传给生成器第 1 段
console.log(iter.next());
console.log(iter.next('bbb')); // 传给生成器第 2 段,作为这一段开始的 yield 语句返回值
console.log(iter.next('ccc')); // 传给生成器第 3 段,作为这一段开始的 yield 语句返回值
console.log(iter.next('ddd')); // 传给生成器第 4 段,作为这一段开始的 yield 语句返回值
/*
aaa
{ value: 111, done: false }
bbb
{ value: 222, done: false }
ccc
{ value: 333, done: false }
ddd
{ value: undefined, done: true }
*/
13.3生成器函数实例
案例1:1s后输出111,2s后输出222,3s后输出333
- 传统方式:嵌套太多,代码复杂,产生 回调地狱。
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333);
}, 3000);
}, 2000);
}, 1000);
- 生成器实现:结构简洁明了
function one() {
setTimeout(() => {
console.log(111);
iter.next();
}, 1000);
}
function two() {
setTimeout(() => {
console.log(222);
iter.next();
}, 2000);
}
function three() {
setTimeout(() => {
console.log(333);
}, 3000);
}
function* generator() {
yield one();
yield two();
yield three();
}
let iter = generator();
iter.next();
//111
//222
//333
案例2:生成器函数模拟每隔1s获取商品数据
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//模拟数据 用户数据 订单数据 商品数据
function getUsers(){
setTimeout(()=>{
let data = '用户数据';
//调用 next 方法,并且将数据传入
iterator.next(data); // 传参给生成器函数的第 2 段,后面类似
},1000);
}
function getOrders(){
setTimeout(()=>{
let data = '订单数据';
iterator.next(data);
},1000);
}
function getGoods(){
setTimeout(()=>{
let data = '商品数据';
iterator.next(data);
},1000);
}
function * gen(){
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
//调用生成器函数
let iterator = gen();
iterator.next()
//用户数据
// 订单数据
// 商品数据
</script>
</body>
</html>
14. Promise学习
14.1 Promise介绍与基本使用
Promise
是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
一个 Promise
必然处于以下几种状态之一:
- 待定(
pending
):初始状态,既没有被兑现,也没有被拒绝。 - 已兑现(
fulfilled
):意味着操作成功完成。 - 已拒绝(
rejected
):意味着操作失败。
Promise
的使用:
- Promise 构造函数:
new Promise((resolve, reject)=>{})
Promise.prototype.then
方法Promise.prototype.catch
方法
一个简单的案例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//实例化 Promise 对象
const p = new Promise( function(resolve,reject){
setTimeout(function(){
// 这个异步请求数据库数据操作是否正确返回数据
let right = true;
if(right){
let data = '数据库中的用户数据';
// // resolve
// 设置 Promise 对象的状态为操作成功
resolve(data);
}else{
let err = '数据读取失败';
// 设置 Promise 对象的状态为操作失败
reject(err);
}
},1000);
} );
//调用promise 对象的 then方法
p.then(function(value){
//执行成功后 走的方法
console.log(value);
},function(reason){
//执行失败后 走的方法
console.log(reason);
})
</script>
</body>
</html>
14.2 Promise封装读取文件
// 使用 nodejs 的 fs 读取文件模块
const fs = require('fs');
const p = new Promise(function (resolve, reject) {
fs.readFile('./resources/为学.txt', (err, data) => {
// err 是一个异常对象
if (err) reject(err);
resolve(data);
})
})
p.then(function (value) {
// 转为字符串输出
console.log(value.toString());
}, function (reason) {
console.log('读取失败!!');
})
14.3 Promise封装AJAX请求
const p = new Promise((resolve, reject) => {
//1.创建对象
const xhr = new XMLHttpRequest();
//2.初始化
xhr.open('get', 'https://api.apiopen.top/getJoke');
//3.发送
xhr.send();
//4.绑定事件,处理响应结果
xhr.onreadystatechange = function () {
//判断
if (xhr.readyState === 4) {
//判断响应状态码 200-299 均为成功
if (xhr.status >= 200 && xhr.status < 300) {
// 表示成功
resolve(xhr.response);
} else {
// 如果 失败
reject(xhr.status);
}
}
}
});
// 指定回调
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
})
14.4 Promise.prototype.then 方法
先复习一下一个 Promise
的三种状态:
- 待定(
pending
):初始状态,既没有被兑现,也没有被拒绝。 - 已兑现(
fulfilled
):意味着操作成功完成。 - 已拒绝(
rejected
):意味着操作失败。
Promise.prototype.then
方法返回的结果依然是 Promise
对象,对象状态由回调函数的执行结果决定。
具体情况如下:
- 若
then
方法没有返回值,则then
方法返回的对象的状态值为成功fulfilled
,返回结果值为undefined
。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('用户数据')
reject('出错了');
}, 1000);
})
// 未设定返回值
const res = p.then((value) => {
console.log(value);
}, (reason) => {
console.warn(reason);
})
// 打印 then 方法的返回值
console.log(res);
打印的结果:
- 如果回调函数中返回的结果是非
Promise
类型的属性,则then
方法返回的对象,其状态为成功(fulfilled
),返回结果值取决于then
方法所执行的是哪个函数(resolve
或reject
)。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('用户数据')
reject('出错了');
}, 1000);
})
// 返回的非 Promise 对象
const res = p.then((value) => {
console.log(value);
return '成功了!!';
}, (reason) => {
console.warn(reason);
return '出错啦!!'
})
// 打印 then 方法的返回值
console.log(res);
打印结果:
- 如果回调函数中返回的结果是
Promise
类型(return new Promise()
),则then
方法返回的Promise
对象状态与该返回结果的状态相同,返回值也相同。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('用户数据')
// reject('出错了');
}, 1000);
})
const res = p.then((value) => {
console.log(value);
// 返回 Promise 对象
return new Promise((resolve, reject) => {
resolve('(1)成功了!!!');
// reject('(1)出错了!!!')
})
}, (reason) => {
console.warn(reason);
return new Promise((resolve, reject) => {
// resolve('(2)成功了!!!');
reject('(2)出错了!!!')
})
})
// 打印 then 方法的返回值
console.log(res);
打印结果:
- 如果回调函数中返回的结果是
throw
语句抛出异常,则then
方法的对象的状态值为rejected
,返回结果值为throw
抛出的字面量或者Error
对象。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('用户数据');
}, 1000);
});
const res = p.then((value) => {
console.log(value);
return new Promise((resolve, reject) => {
throw new Error('错误了!!');
})
});
// 打印结果
console.log(res);
打印结果如下:
链式调用:
Promise.prototype.then
方法返回的结果还是 Promise
对象,这意味着我们可以继续在该结果上使用 then
方法,也就是链式调用。
const p = new Promise(resolve=>{}, reject=>{});
p.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
...
14.5 Promise链式调用练习--多个文件读取
const fs = require('fs');
let p = new Promise((resolve, reject) => {
fs.readFile('./resources/users.md', (err, data) => {
// 传给下一轮文件读取操作
resolve(data);
})
});
p.then(value => {
return new Promise((resolve, reject) => {
// value 为第一次读取的文件数据,data 为第二次(当前)读取的数据
fs.readFile('./resources/orders.md', (err, data) => {
// 将上轮读取结果和本轮合并传到下一轮轮读取操作
resolve([value, data]);
});
});
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./resources/goods.md', (err, data) => {
// value 为上一轮传递过来的文件数据数组
value.push(data);
// 传给下一轮操作
resolve(value);
});
});
}).then(value => {
// 合并数组元素,输出
console.log(value.join('\n'));
});
14.5 Promise对象catch方法
catch()
方法返回一个 Promise
,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected)
相同。
举例:
var p1 = new Promise(function (resolve, reject) {
resolve('Success');
});
p1.then(function (value) {
console.log(value); // "Success!"
throw 'oh, no!';
}).catch(function (e) {
console.log(e); // "oh, no!"
}).then(function () {
console.log('有 catch 捕获异常,所以这句输出');
}, function () {
console.log('没有 catch 捕获异常,这句将不会输出');
});
//以下两种方法 等价
p.then(function(value){},function(reason){
console.error(reason);
})
p.catch(function(reason){
console.warn(reason);
})
输出结果:
Success
oh, no!
有 catch 捕获异常,所以这句输出
以上只是 Promise 的入门,更多还要进一步深入学习。
15.Set
15.1 Set的定义和使用
ES6 提供了新的数据结构 Set
(集合)。它类似于数组,但 成员的值都是唯一的,集合实现了 iterator
接口,所以可以使用『扩展运算符』和『for...of
』进行遍历。
定义一个 Set 集合:
let st1 = new Set();
let st2 = new Set([可迭代对象]);
集合(这里假设有一个集合 st
)的属性和方法:
st.size
:返回集合个数st.add(item)
:往集合中添加一个新元素item
,返回当前集合st.delete(item)
:删除集合中的元素,返回boolean
值st.has(item)
:检测集合中是否包含某个元素,返回boolean
值st.clear()
:清空集合- 集合转为数组:
[...st]
- 合并两个集合:
[...st1, ...st2]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//声明一个 set
let s = new Set();
let s1 = new Set(['大事儿','小事儿','坏事儿']);
//元素个数
console.log(s1.size) //3
//添加元素
s1.add('美事儿');
//删除元素
s1.delete('大事儿')
//检测
console.log(s1.has('糟心事'));
//清空
// s1.clear();
// console.log(s1);
//遍历
for(let v of s1){
console.log(v);
}
</script>
</body>
</html>
15.2 集合案例
案例1: 数组去重
let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
let res1 = [...new Set(arr1)];
console.log(res1); // [ 1, 2, 3, 4 ]
案例2:数组求交集
let arr2_1 = [1, 2, 2, 3, 4, 5];
let arr2_2 = [3, 6, 6, 7, 1, 4];
let res2 = arr2_1.filter(v => new Set(arr2_2).has(v))
console.log(res2); // [ 1, 3, 4 ]
案例3:数组求并集
let arr3_1 = [1, 2, 2, 3, 4, 5];
let arr3_2 = [3, 6, 6, 7, 1, 4];
let res3 = [...new Set([...arr3_1, ...arr3_2])];
console.log(res3); // [ 1, 2, 3, 4, 5, 6, 7 ]Copy to clipboardErrorCopied
案例4:数组求差集
let arr4_1 = [1, 2, 2, 3, 4, 5];
let arr4_2 = [3, 6, 6, 7, 1, 4];
let res4 = [...new Set(arr4_1)].filter(v => !(new Set(arr4_2).has(v)))
console.log(res4); // [ 2, 5 ]
16.Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是 “键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for...of』进行遍历。
Map 的属性和方法:(k
为键,v
为值)
size
:返回 Map 的元素(键值对)个数set(k, v)
:增加一个键值对,返回当前 Mapget(k)
:返回键值对的键值has()
:检测 Map 中是否包含某个元素clear()
:清空集合,返回undefined
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//声明 map
let m = new Map();
//添加元素
m.set('name','尚硅谷');
m.set('change',function(){
console.log("我们可以改变你!!");
});
let key = {
school : 'ATGUIGU'
};
m.set(key,['北京','上海','深圳']);
console.log(m.size);
//删除
// m.delete('name');
//获取
console.log(m.get('change'))
console.log(m.get(key))
//清空
// m.clear()
//遍历
for(let v of m){
console.log(v);
}
</script>
</body>
</html>
17.class类
这部分在 JS 高级也涉及,故可以前往 JS 高阶 class 学习。,那部分的笔记更加详细,有原理。所以这一节后面部分只给出例子。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class 关键字,可以定义类。基本上,ES6 的 class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
class Phone{
//构造方法 名字不能修改
constructor(brand,price){
this.brand = brand;
this.price = price;
}
//方法必须使用 该语法, 不能使用 ES5的对象完整形式
call(){
console.log("我可以打电话!");
}
}
let OnePlus = new Phone("1+", 1999);
console.log(OnePlus);
</script>
</body>
</html>
17.1 es5构造函数继承
//手机
function Phone(brand, price){
this.brand = brand;
this.price = price;
}
Phone.prototype.call = function(){
console.log("我可以打电话");
}
//智能手机
function SmartPhone(brand, price, color, size){
Phone.call(this, brand, price);
this.color = color;
this.size = size;
}
//设置子级构造函数的原型
SmartPhone.prototype = new Phone;
// 矫正 constructor 指向
SmartPhone.prototype.constructor = SmartPhone;
//声明子类的方法
SmartPhone.prototype.photo = function(){
console.log("我可以拍照")
}
SmartPhone.prototype.playGame = function(){
console.log("我可以玩游戏");
}
const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');
console.log(chuizi);
17.2 class的类继承 (extends) 和方法的重写
ES6 中直接使用 extends
语法糖(更简洁高级的实现方式)来实现继承,同时可以重写父类的方法,直接在子类中重新写一次要重写的方法即可覆盖父类方法。
class Phone{
//构造方法
constructor(brand, price){
this.brand = brand;
this.price = price;
}
//父类的成员属性
call(){
console.log("我可以打电话!!");
}
}
class SmartPhone extends Phone {
//构造方法
constructor(brand, price, color, size){
super(brand, price);// Phone.call(this, brand, price)
this.color = color;
this.size = size;
}
photo(){
console.log("拍照");
}
// 方法的重写
call(){
console.log('我可以进行视频通话');
}
}
const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
xiaomi.call();
xiaomi.photo();
17.3 getter和setter
实际上,getter
和 setter
是 ES5(ES2009)提出的特性,这里不做详细说明,只是配合 class
使用举个例子。
当属性拥有 get
/set
特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter
/setter
函数。
使用场景: getter
是一种语法,这种 get
将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter
则是在修改某一属性时所给出的相关提示。
class Test {
constructor(log) {
this.log = log;
}
get latest() {
console.log('latest 被调用了');
return this.log;
}
set latest(e) {
console.log('latest 被修改了');
this.log.push(e);
}
}
let test = new Test(['a', 'b', 'c']);
// 每次 log 被修改都会给出提示
test.latest = 'd';
// 每次获取 log 的最后一个元素 latest,都能得到最新数据。
console.log(test.latest);
以上输出:
latest 被修改了
latest 被调用了
[ 'a', 'b', 'c', 'd' ]
18. es6的数值扩展
1.Number.EPSILON
是 JavaScript 表示的最小精度,一般用来处理浮点数运算。例如可以用于两个浮点数的比较。
let equal = (x, y) => Math.abs(x - y) < Number.EPSILON;
console.log(0.1 + 0.2 === 0.3); // false
console.log(equal(0.1 + 0.2, 0.3)); // true
2.二进制和八进制:二进制以 0b
开头,八进制以 0o
开头。
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(x);
3.Number.isFinite
检测一个数值是否为有限数。
console.log(Number.isFinite(100)); // false
console.log(Number.isFinite(100 / 0)); // true
console.log(Number.isFinite(Infinity)); // false
4.Number.parseInt 和 Number.parseFloat (字符串转化为整数)
ES6 给 Number
添加了 parseInt
方法,Number.parseInt
完全等同于 parseInt
。将字符串转为整数,或者进行进制转换。Number.parseFloat
则等同于 parseFloat()
Number.parseInt === parseInt; // true
Number.parseFloat === parseFloat; // true
console.log(Number.parseInt('5211314love')); // 5211314
console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
5.Number.isInteger()
判断一个数是否为整数。
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(2.5)); // false
6.Math.trunc()` 将数字的小数部分抹掉。
console.log(Math.trunc(3.5)); // 3
7.Math.sign` 判断一个数到底为正数 负数 还是零
console.log(Math.sign(100)); //1
console.log(Math.sign(0)); //0
console.log(Math.sign(-50)); //-1
19.es6的对象方法扩展
ES6 新增了一些 Object
对象的方法。
- Object.is 比较两个值是否严格相等,与『===』行为 基本一致
Object.assign
对象的合并,将源对象的所有可枚举属性,复制到目标对象__proto__
、setPrototypeOf
、setPrototypeOf
可以直接设置对象的原型
19.1 Object.is
Object.is()
方法判断两个值是否完全相同。Object.is
比较两个值是否严格相等,与 ===
行为 基本一致。返回一个 Boolean
类型。
Object.is(value1, value2);
Object.is()
方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:
- 都是
undefined
- 都是
null
- 都是
true
或false
- 都是相同长度的字符串且相同字符按相同顺序排列
- 都是相同对象(意味着每个对象有同一个引用)
- 都是数字且
- 都是
+0
- 都是
-0
- 都是
NaN
- 或都是非零而且非
NaN
且为同一个值
- 都是
与 ==
运算不同。 ==
运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换 (这种行为的结果会将 "" == false
判断为 true
),而 Object.is
不会强制转换两边的值。
与 ===
算也不相同。 === 运算符 (也包括 ==
运算符) 将数字 -0
和 +0
视为相等,而将 Number.NaN
与 NaN
视为不相等。
19.2 Object.assign
Object.assign
对象的合并,相当于浅拷贝。
const config1 = {
host: 'localhost',
port: 3306,
name: 'root',
pass: 'root',
test: 'test'
};
const config2 = {
host: 'http://atguigu.com',
port: 33060,
name: 'atguigu.com',
pass: 'iloveyou',
test2: 'test2'
}
console.log(Object.assign(config1, config2));
19.3 Object.setPrototypeOf 和Object.getPrototypeOf
Object.setPrototypeOf
用于设置对象的原型对象,Object.getPrototypeof
用于获取对象的原型对象,相当于 __proto__
。
const school = {
name: '尚硅谷'
}
const cities = {
xiaoqu: ['北京','上海','深圳']
}
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);
20.ES6模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
20.1 模块化介绍
模块化的好处
模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
模块化规范产品
ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AMD => requireJS
- CMD => seaJS
20.2 ES6模块化语法
模块功能主要由两个命令构成:export
和 import
。
export
命令用于规定模块的对外接口import
命令用于输入其他模块提供的功能
20.2.1 模块导出数据语法
-
单个导出
// 单个导出 export let uname = 'Rick'; export let sayHello = function () { console.log('Hi, bro!'); }Copy to clipboardErrorCopied
-
合并导出
let uname = 'Rick'; let sayHello = function () { console.log('Hi, bro!'); } // 合并导出 export { uname, sayHello };
-
默认导出
// 默认导出
export default {
uname: 'Rick',
sayHello: function () {
console.log('Hi, bro!');
}
}
20.2.2 模块导入数据语法
-
通用导入方式
import * as m1 from './js/m1.js'; import * as m2 from './js/m2.js'; import * as m3 from './js/m3.js';Copy to clipboardErrorCopied
-
解构赋值导入
import { uname, sayHello } from './js/m1.js';
// 有重复名可以设置别名
import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
console.log(uname);
// 配合默认导出
import {default as m3} from "./src/js/m3.js";Copy to clipboardErrorCopied
-
简便方式导入,针对默认暴露
import m3 from "./src/js/m3.js";
20.2.3 ES6 使用模块化方式二
将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下:
import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';
console.log(m1);
console.log(m2);
console.log(m3);
在 index.html 中引入 app.js 文件内容:
<script src="./app.js" type="module"></script>Copy to clipboardErrorCopied
20.3 使用babel 对象模块化代码转换
babel官网: https://www.babeljs.cn/
有的浏览器不支持 ES6 语法,这时候就需要使用 babel 来将其转换成 ES5 等价语法。
-
安装工具
npm i babel-cli babel-preset-env browserify(webpack) -DCopy to clipboardErrorCopied
-
编译
npx babel src/js -d dist/js --presets=babel-preset-envCopy to clipboardErrorCopied
-
打包
npx browserify dist/js/app.js -o dist/bundle.jsCopy to clipboardErrorCopied
24.4 ES6模块化引入NPM包
npm install jquery
再通过 import
导入即可。
import $ from 'jquery'
三、ES7新特性
-
Array.prototype.includes
includes
方法用来检测数组中是否包含某个元素,返回布尔类型值。 -
指数运算符
在 ES7 中引入指数运算符**
,用来实现幂运算,功能与Math.pow(a, b)
结果相同。
2 ** 3 // 8
Math.pow(2, 3) // 8
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
const mingzhu = ['西游记','红楼梦','三国演义','水浒传'];
//判断
console.log(mingzhu.includes('西游记')); //true
console.log(mingzhu.includes('金瓶')); //false
//平方 **
console.log(2 ** 4); //16
console.log(Math.pow(2,4)); //16
</script>
</body>
</html>
四、ES8新特性
4.1 async 和 await
async
和 await
两种语法结合可以让异步代码像同步代码一样。(即:看起来是同步的,实质上是异步的。)
先从字面意思理解,async
意为异步,可以用于声明一个函数前,该函数是异步的。await
意为等待,即等待一个异步方法完成。
4.2 async
async
声明(function
)的函数成为 async 函数,语法:
async function funcName() {
//statements
}
async
内部可以使用 await
,也可以不使用。 async
函数的返回值是一个 Promise
对象,因此执行这个函数时,可以使用 then
和 catch
方法。 根据 函数体内部 的返回值, async
函数返回值具体情况如下:
- 函数体内不返回任何值,则
async
函数返回值为一个成功(fulfilled
)的Promise
对象,状态值为undefined
。
let a = async function() {}
let res = a()
console.log(res)
// Promise{<fullfilled>: undefined}
- 返回结果不是一个
Promise
,则async
函数返回值为一个成功(fulfilled
)的Promise
对象,状态值为这个内部返回值。
let a = async function () {
return 'hello'
}
let res = a()
console.log(res)
// Promise{<fullfilled>: 'hello'}
- 内部抛出错误,则
async
函数返回值为一个失败的Promise
对象。
let a = async function foo() {
throw new Error('出错了')
}
a().catch(reason => {
console.log(reason)
})
- 若函数内部返回值是一个
Promise
对象,则async
函数返回值的状态取决于这个Promise
对象。
let a = async function () {
return new Promise((resolve, reject) => {
resolve("成功")
})
}
a().then(value => {
console.log(value)
})
4.3 await
await
相当于一个运算符,右边接一个值。一般为一个 Promise
对象,也可以是一个非 Promise
类型。当右接一个非 Promise
类型,await
表达式返回的值就是这个值;当右接一个 Promise
对象,则 await
表达式会阻塞后面的代码,等待当前 Promise
对象 resolve
的值。
综合 async
和 await
而言。await
必须结合 async
使用,而 async
则不一定需要 await
。 async
会将其后的函数的返回值封装成一个 Promise
对象,而 await
会等待这个 Promise
完成,然后返回 resolve
的结果。当这个 Promise
失败或者抛出异常时,需要时使用 try-catch
捕获处理。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//创建Promise对象
const p = new Promise((resolve,reject)=>{
// resolve("用户数据");
reject("失败啦");
})
//await 要放在 async 函数中
async function main(){
try{
let result = await p ;
console.log(result); //用户数据
}catch(e){
console.log(e); //失败啦
}
}
//调用函数
main();
</script>
</body>
</html>
4.4 async 和 await 结合读取文件
需求:先读取用户数据 user,然后读取订单数据 order,最后读取商品数据 goods。
对于这种异步操作很容易想到使用 Promise
,代码如下:
const fs = require('fs')
let p = new Promise((resolve, reject) => {
fs.readFile('./files/user.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
p.then(value => {
return new Promise((resolve, rejecet) => {
fs.readFile('./files/order.md', (err, data) => {
if (err) rejecet(err)
resolve([value, data])
})
})
}, reason => {
console.log(reason)
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./files/goods.md', (err, data) => {
if (err) reject(err)
value.push(data)
resolve(value)
})
})
}, reason => {
console.log(reason)
}).then(value => {
console.log(value.join('\n'))
}, reason => {
console.log(reason)
})
但是,使用 Promise
链式调用虽然避免了回调地狱,但这种链式调用过多难免引起代码复杂,看起来不直观。可以使用 async
和 await
方法优化,代码如下:
const fs = require('fs')
function readUser() {
return new Promise((resolve, reject) => {
fs.readFile('./files/user.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
function readOrder() {
return new Promise((resolve, reject) => {
fs.readFile('./files/order.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
function readGoods() {
return new Promise((resolve, reject) => {
fs.readFile('./files/goods.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
//声明一个async 函数
async function read() {
let user = await readUser()
let order = await readOrder()
let goods = await readGoods()
console.log([user, order, goods].join('\n'))
}
read()
这样,代码看起来很直观,就好像是同步代码一样,实际上是异步操作。
4.5async 和 await 结合发送AJAX请求
function sendAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
}
reject(xhr.status)
}
}
})
}
//promise 方法测试
sendAjax("https://api.apiopen.top/getJoke").then(value=>{
console.log(value);
},reason=>{})
async function main() {
let res = await sendAjax('https://api.apiopen.top/getJoke');
// let poem = res.result.name + '——' + res.result.from
// document.body.innerText = poem
console.log(res);
}
main()
4.6 es8对象方法扩展 Object.values 和Object.entries
Object.values()
方法返回一个给定对象的所有可枚举属性值的数组,类似于Object.keys()
,只是前者返回属性值,后者返回键值组合的数组。Object.entries()
方法返回一个给定对象自身可遍历属性[key,value]
的数组(数组元素也是一个个的数组的数组) 返回的是一个数组,这样就可以使用for...of
遍历了。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//声明对象
const school = {
name:"尚硅谷",
cities:['北京','上海','深圳'],
xueke:['java','前端','大数据']
}
//获取对象所有的键
// console.log(Object.keys(school)); //(3) ['name', 'cities', 'xueke']
//获取对象所有的值
// console.log(Object.values(school)); //(3) ['尚硅谷', Array(3), Array(3)]
//entyies
// console.log(Object.entries(school)) / [Array(2), Array(2), Array(2)]
//创建map
const m = new Map(Object.entries(school));
console.log(m.get('name')) //
console.log(m.get('cities')) // ['北京', '上海', '深圳']
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptor(school));
const obj = Object.create(null,{
name:{
//设置值
value:'尚硅谷',
//属性特性
writable:true,
configurable:true,
enumerable:true
}
})
</script>
</body>
</html>
五、es9新特性
5.1 es9扩展运算符与rest参数
<script>
/**
rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过ES6中只针对于数组,
在es9中未对象提供了像数组一样的 rest 参数和扩展运算符
*/
function connect({host,port,...user}){
console.log(host);
console.log(port);
console.log(user);
}
connect({
host:'127.0.0.1',
port:3306,
username:'root',
password:'root',
type:'master'
})
</script>
const skillOne={
q:'天音波'
}
const skillTwo={
w:'波引起四起'
}
const skillThree={
e:'神龙摆尾'
}
const skillFour={
r:'天籁之音'
}
const mangseng = {...skillOne,...skillTwo,...skillThree,...skillFour,}
console.log(mangseng);//{q: '天音波', w: '波引起四起', e: '神龙摆尾', r: '天籁之音'}
5.2es9正则扩展-命名捕获分组
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
//声明一个字符串
let str = '<a href="http://www.atguigu.com">尚硅谷</a>';
//提取 url 与 [标签文本]
const reg = /<a href="(.*)">(.*)<\/a>/;
//执行
const result = reg.exec(str);
console.log(result);
console.log(result[1]); //http://www.atguigu.com
console.log(result[2]); //尚硅谷
let str1 = '<a href="http://www.atguigu.com">尚硅谷</a>';
const reg1 = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;
const result1 = reg1.exec(str1);
console.log(result1)
console.log(result1.groups.url) //http://www.atguigu.com
console.log(result1.groups.text) //尚硅谷
</script>
</body>
</html>
5.3 es9正则扩展-反向断言
<body>
<script>
let str = "JS5211314你知道吗555啦啦啦";
//正向断言
const reg = /\d+(?=啦)/;
const result = reg.exec(str);
console.log(result);
//反向断言
const reg1 = /(?<=吗)\d+/;
const result1 = reg1.exec(str);
console.log(result1);
</script>
</body>
5.4 es9正则扩展--dotAll模式
dotAll可以省去 ul标签或html文本中的点 ,应用起来更方便
六、es10新特性
6.1对象扩展方法 Object.fromEntries
Object.fromEntries() 方法把可迭代对象的键值对列表转换为一个对象。
语法:
Object.fromEntries(iterable)
iterable
:类似 Array 、 Map 或者其它实现了可迭代协议的可迭代对象。- 返回值:一个由该迭代对象条目提供对应属性的新对象。
- 相当于
Object.entries
(ES8)的逆运算。
const mp = new Map([
[1, 2],
[3, 4]
])
const obj = Object.fromEntries(mp)
console.log(obj)
// { '1': 2, '3': 4 }
const arr = [[1, 2]]
console.log(Object.fromEntries(arr))
// {'1': 2}
6.2字符串方法扩展trimStart() 和 trimEnd()
trimStart()
去除字符串开头连续的空格(trimLeft
是此方法的别名)trimEnd()
去除字符串末尾连续的空格(trimRight
是此方法的别名)
<script>
let str = ' iloveyou ';
console.log(str);
console.log(str.trimStart());
console.log(str.trimEnd());
</script>
运行结果:
6.3 数组方法扩展 flat 和 flatMap
Array.prototype.flat(i)
:展平一个多维数,i
为要展开的层数,默认为1,即展开一层。
使用 Infinity
作为深度,展开任意深度的嵌套数组
[1, [2, 3, [4, 5]]].flat(Infinity)
// [1, 2, 3, 4, 5, 6]
也可以使用 flat
来去除数组空项
let arr = [1,2,3,,4]
arr.flat() // [1,2,3,4]
Array.prototype.flatMap
:相当于map
和flat
的结合,方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
<script>
//flat 翻译: 平
//将多维数组转化为低维数组
const arr=[1,2,3,4];
console.log(arr); // [1, 2, 3, 4]
const arr1=[1,2,3,4,[5,6]];
console.log(arr1) //[1, 2, 3, 4, Array(2)]
const arr2=[1,2,3,4,[5,6,[7,8,9]]];
//参数为深度 是一个数字
console.log(arr2.flat(1)); //[1, 2, 3, 4, 5, 6, Array(3)]
console.log(arr2.flat(2)); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
//flatMap 将多维数组 转化为一维
const arr3 = [1,2,3,4];
const result = arr.flatMap(item =>[item*10]);
console.log(result); // [10, 20, 30, 40]
</script>
输出结果:
6.4 Symbol.prototype.description
使用 Symbol()
创建的 Symbol
字面量,可以直接使用 description
获取该字面量的描述。
let sym = Symbol('hello')
console.log(sym.description)
// hello
七、es11新特性
7.1 类的私有属性
ES11 提供了类的私有属性,在类的外部无法访问该属性。只有再类的内部能访问。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
class Person{
//公有属性
name;
//私有属性
#age;
#weight;
//构造方法
constructor(name,age,weight){
this.name = name;
this.#age = age;
this.#weight = weight;
}
intro(){
console.log(this.name);
console.log(this.#age);
console.log(this.#weight);
}
}
//实例化
const girl = new Person('晓红',18,'45kg');
// console.log(girl);
// 外部无法直接访问
// console.log(girl.name);
// console.log(girl.#age);
// console.log(girl.#weight);
girl.intro();
//晓红
// 18
// 45kg
</script>
</body>
</html>
7.2 Promise.allSettled方法
该 Promise.allSettled()
方法返回一个在所有给定的 promise
都已经 fulfilled
或 rejected
后的 promise
,并带有一个对象数组,每个对象表示对应的 promise
结果。allSettled
方法返回的 Promise
对象始终是成功(fulfilled
)的。
使用场景:
- 有多个彼此不依赖的异步任务成功完成时使用。
- 想得到每个
promise
的结果时使用。
对比于 Promise.all()
,all()
也接受一个 Promise
对象数组参数,只要有一个失败(rejected
),那么返回的 Promise
对象就是失败(rejected
)的。
使用场景:
- 传进去的
Promise
对象彼此依赖,且需要在其中任何一个失败的时候停止。
两个 Promise
都是成功的情况:
let p1 = new Promise((resolve, reject) => {
resolve('用户数据-1')
})
let p2 = new Promise((resolve, reject) => {
resolve('订单数据-2')
})
let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)
输出结果:
一个成功,一个失败:
let p1 = new Promise((resolve, reject) => {
resolve('用户数据-1')
})
let p2 = new Promise((resolve, reject) => {
reject('失败了')
})
let res1 = Promise.allSettled([p1, p2])
let res2 = Promise.all([p1, p2])
console.log(res1)
console.log(res2)
输出结果:
7.3 String.prototype.matchAll方法
matchAll()
方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
<script>
//声明正则
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array); // [Array(4), Array(4)]
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
</script>
7.4 可选链操作符
可选链 ?.
是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。
原则:如果可选链 ?.
前面的部分是 undefined
或者 null
,它会停止运算并返回该部分。
let user = {
address: {
}
}
console.log( user?.address?.street ); // undefined(不报错)
短路效应:
短路效应:正如前面所说的,如果 ?.
左边部分不存在,就会立即停止运算(“短路效应”)。所以,如果后面有任何函数调用或者副作用,它们均不会执行。
这有和 &&
的作用类似,但上述改用 &&
会显得代码冗余度高:
console.log(user && user.address && user.address.stree)
其他变体: ?.( ) ,?.[ ]
可选链 ?.
不是一个运算符,而是一个特殊的语法结构。它还 可以与函数和方括号一起使用。
例如,将 ?.()
用于调用一个可能不存在的函数(即使不存在也不报错)。
function foo() {
console.log('hello')
}
foo?.()
// hello
?.[]
允许从一个可能不存在的对象上安全地读取属性。(即使不存在也不报错)。
let obj = {
key: 123
}
console.log(obj?.['key'])
// 123
7.5 动态 import
const btn = document.getElementById('btn');
btn.onclick = function(){
import('./hello.js').then(module => {
module.hello();
)};
}
7.6 Bigint类型
BigInt
是一种特殊的数字类型,它提供了对任意长度整数的支持。
创建 bigint
的方式有两种:在一个整数字面量后面加 n
或者调用 BigInt
函数,该函数从字符串、数字等中生成 bigint
。
let n1 = 123n
let n2 = 456n
let n3 = BigInt(789)
console.log(typeof n1) // bigint
console.log(n1+n2) // 579n
console.log(n2+n3) // 1245n
//大数值运算
let max = Number.MAX_SAFE_INTEGER;
console.log(max); //9007199254740991
console.log(max + 1); //9007199254740992
console.log(max + 2); //9007199254740992
console.log(BigInt(max)); //9007199254740991n
console.log(BigInt(max) + BigInt(1)); //9007199254740992n
console.log(BigInt(max) + BigInt(2)); //9007199254740993n
比较运算符:
- 例如
<
和>
,使用它们来对bigint
和number
类型的数字进行比较没有问题:
alert( 2n > 1n ); // true
alert( 2n > 1 ); // true
- 但是请注意,由于
number
和bigint
属于不同类型,它们可能在进行==
比较时相等,但在进行===
(严格相等)比较时不相等:
alert( 1 == 1n ); // true
alert( 1 === 1n ); // false
7.7 绝对全局对象globalThis
全局对象提供可在任何地方使用的变量和函数。默认情况下,这些全局变量内置于语言或环境中。
在浏览器中,它的名字是 window
,对 Node.js 而言,它的名字是 global
,其它环境可能用的是别的名字。
ES11中 globalThis
被作为全局对象的标准名称加入到了 JavaScript 中,所有环境都应该支持该名称。所有主流浏览器都支持它。
使用场景: 假设我们的环境是浏览器,我们将使用 window
。如果你的脚本可能会用来在其他环境中运行,则最好使用 globalThis
。