文章目录
- 一 ECMAScript 相关介绍
- 1 什么是 ECMA
- 2 什么是 ECMAScript
- 3 什么是 ECMA-262
- 4 谁在维护 ECMA-262
- 5 为什么要学习 ES6
- 6 ES6 兼容性
- 二 ECMASript 6 新特性
- 1. let 关键字
- 案例
- 2. const 关键字
- **3. 变量的解构赋值**
- 应用场景:
- 4. 模板字符串
- 应用场景:
- 5. 简化对象写法
- 应用场景:
- 6. 箭头函数
- 案例1:箭头函数 this 始终指向声明时所在作用域下 this 的值,call 等方法无法改变其指向
- 案例2:筛选偶数
- 案例3:点击 div两秒后变成粉色
- 7. 函数参数默认值设定
- 8. rest 参数
- 应用场景:
- 案例1:求不定个数数字的和
- 9. spread 扩展运算符
- 10. Symbol
- 10.1 Symbol 基本介绍与使用
- Symbol 的特点
- Symbol 的创建
- 10.2 对象添加 Symbol 类型的属性
- 10.3 Symbol 内置值
- 11. 迭代器
- 11.1 定义
- 11.2 工作原理
- 11.3 自定义遍历数据
- 12. Generator 生成器函数
- 12.1 生成器函数的声明和调用
- **12.2 生成器函数的参数传递**
- 2.3 生成器函数案例
- 13. Promise
- 13.1 Promise 的定义和使用
- **13.2 Promise 封装读取文件**
- **13.3 Promise 封装 Ajax 请求**
- **13.4 Promise.prototype.then 方法**
- 13.4 链式调用
- 13.5 Promise.prototype.catch
- **13.6 链式调用练习-多个文件读取**
- 14. Set
- 15. Map
- 16. class 类
- 16.1 复习 ES5 function 构造函数 的继承
- **16.2 extends 类继承和方法的重写**
- 16.3 静态成员
- 16.4 getter 和 setter
- 17. 数值扩展
- 18. 对象方法扩展
- 18.1 Object.is()
- 18.2 Object.assign
- 18.3 Object.setPrototypeOf 和 Object.getPrototypeof
- 19. ES6 模块化
- 19.1 模块化的好处
- 19.2 模块化规范产品
- 19.3 ES6 模块化语法
- **1 模块导出数据语法**
- 1 分别暴露
- 2 统一暴露
- 3 默认暴露
- 2 模块导入数据语法
- 1 通用导入方式
- 2 解构赋值导入
- 3. 简便方式导入,只能用于默认暴露
- **3 ES6 使用模块化方式二**
- **19.4 使用 babel 对模块化代码转换**
- 1. 安装工具
- 2. 编译
- 3 打包
- **19.5 ES6 模块化引入 npm 安装的包**
- 三 ECMASript 7 新特性
- 1. Array.prototype.includes
- 2. 指数运算符
- **四 ECMAScript 8 新特性**
- 1. async 和 await
- 1.1 async
- **1.2 await**
- **1.3 综合应用-读取文件**
- **1.4 综合应用-封装ajax**
- **2. Object.values 和 Object.entries**
一 ECMAScript 相关介绍
1 什么是 ECMA
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准,1994 年后该组织改名为Ecma 国际
2 什么是 ECMAScript
ECMAScript 是由 Ecma 国际通过ECMA-262 标准化的脚本程序设计语言。
ECMASCRIPT
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
4 谁在维护 ECMA-262
TC39(Technical Committee 39)是推进ECMAScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。
5 为什么要学习 ES6
-
ES6 的版本变动内容最多,具有里程碑意义
-
ES6 加入许多新的语法特性,编程实现更简单、高效
-
ES6 是前端发展趋势,就业必备技能
6 ES6 兼容性
可查看兼容性:http://kangax.github.io/compat-table/es6/
二 ECMASript 6 新特性
1. let 关键字
let
关键字用来声明变量,使用 let
声明的变量有几个特点
-
不允许重复声明
-
块级作用域
-
不存在变量提升
-
不影响作用域链
应用场景:以后声明变量使用let 就对了
案例
- 给多个 div 循环注册点击事件
- 错误示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<h2 class="page-header">点击切换颜色</h2> <div class="item"></div> <div class="item"></div> <div class="item"></div> <script> // 错误示例,divs.length === 3 document.addEventListener('DOMContentLoaded', function () { let divs = document.querySelectorAll('div'); for (var i = 0; i < divs.length; i++) { divs[i].addEventListener('click', function () { divs[i].style.backgroundColor = 'pink'; }); } }); /* i 为当前作用域下的共享变量。 当每次点击 div 的时候,各个点击事件共享 i 的值,此时 i = 3,将报错 */ </script>
正确实例:将以上代码中的 var 改为 let。
- 1s 后循环输出所有数字
- 错误示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18for (var i = 1; i <= 5; i++) { setTimeout(() => { console.log(i); }, 1000); } /* 输出:6 6 6 6 6 循环从1-5的时间很短暂,远不及 1s。 此时五个异步事件瞬间加入到异步事件队列中,等待 1s后依次执行。 而此时i为6,故瞬间输出 5 个 6。 异步事件队头 (1) console.log(i); (2) console.log(i); (3) console.log(i); (4) console.log(i); (5) console.log(i); */
- 正确示例
1
2
3
4
5
6
7
8
9for (let j = 1; j <= 5; j++) { setTimeout(() => { console.log(j); }, 1000); } // 输出:1 2 3 4 5 // let 有块级作用域,每个 j 都会形成一个自己的块级作用域,与相应的异步事件共享: // {j = 1;} {j = 2;} {j = 3;} {j = 4;} {j = 5;}
- 解决方法2
1
2
3
4
5
6
7
8
9// 给每一个 i 设置一个立即执行函数,会形成自己的块级作用域,不影响外部变量。 for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(() => { console.log(i); }, 1000); })(i); }
2. const 关键字
const
关键字用来声明常量,const
声明有以下特点
-
声明必须赋初始值
-
标识符一般为大写
-
不允许重复声明
-
值不允许修改
-
块级作用域
注意:对象属性修改和数组元素变化不会出发 const 错误
应用场景:声明对象类型使用 const,非对象类型声明选择 let
1
2
3
4
5
6
7
8
9
10const arr = [1, 2, 3, 4]; arr.push(5, 6); console.log(arr); // 不报错 const obj = { uname: 'rick', age: 30 } obj.age = 40; // 只要不改变地址,就不报错
3. 变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为 解构赋值
数组的解构赋值
1
2
3const arr = ['red', 'green', 'blue']; let [r, g, b] = arr;
对象的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13const obj = { uname: 'rick', age: 30, sayHi: function () { console.log('hello'); }, sayBye() { console.log('Bye~'); } } let {name, age, sayHi} = obj; let {sayBye} = obj;
应用场景:
频繁使用对象方法、数组元素,就可以使用解构赋值形式
4. 模板字符串
模板字符串(template string)是增强版的字符串,用反引号 `` `标识,特点
-
字符串中可以出现换行符
-
可以使用
${xxx}
形式输出变量,近似 EL 表达式
应用场景:
当遇到字符串与变量拼接的情况使用模板字符串
1
2
3
4
5
6
7
8
9let name = 'jack'; console.log(`hello, ${name}`); let ul = `<ul> <li>apple</li> <li>banana</li> <li>peach</li> </ul>`
5. 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁
应用场景:
对象简写形式简化了代码,所以以后用简写就对了
1
2
3
4
5
6
7
8
9
10
11
12
13let uname = 'rick'; let age = 30; let sayHi = function () { console.log('Hi'); } // 创建对象,因属性、方法 的 k v 同名,可以简化 const obj = { uname, age, sayHi() { console.log('Hi'); } }
6. 箭头函数
ES6 允许使用箭头=>
定义函数
-
function 写法
复制代码1
2
3
4
5function fn(param1, param2, …, paramN) { // 函数体 return expression; }
-
=>
写法
1
2
3
4
5let fn = (param1, param2, …, paramN) => { // 函数体 return expression; }
注意
- 如果形参只有一个,小括号可以省略
- 如果函数体只有一条语句,花括号可以省略,函数的返回值为该条语句的执行结果,如果是 return 语句,return 必须省略
- 箭头函数 this 是静态的,始终指向声明时所在作用域下 this 的值
this
就是调用方法的那个对象箭头函数 this 始终指向声明时所在作用域下 this 的值(不停的往上找),call 等方法无法改变其指向
- 箭头函数不能作为构造函数实例化
- 不能使用 arguments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 省略小括号 let fn1 = n => { return n * n; } // 省略花括号 let fn2 = (a + b) => a + b; // 箭头函数 this 始终指向声明时所在作用域下 this 的值 const obj = { a: 10, getA () { let fn3 = () => { console.log(this); // obj {...} console.log(this.a); // 10 } fn3(); } }
案例1:箭头函数 this 始终指向声明时所在作用域下 this 的值,call 等方法无法改变其指向
1
2
3
4
5
6
7
8
9
10
11let obj = { uname: 'rick', age: 30 }; let foo = () => { console.log(this) } let bar = function () { console.log(this) } // call 对箭头函数无效 foo.call(obj); // window bar.call(obj); // obj {...}
案例2:筛选偶数
1
2
3
4
5
6
7
8
9
10
11
12
13const arr = [1, 6, 9, 10, 100, 25] // const result = arr.filter(function(item) { // if(item % 2 === 0) { // return true; // } else { // return false; // } // }); const result = arr.filter(item => item % 2 === 0); console.log(result);
案例3:点击 div两秒后变成粉色
- 方案1:使用 _this 保存 div 下的 this,从而设置 div 的 style 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<style> div { width: 200px; height: 200px; background: #58a; } </style> <div id="ad"></div> <script> let ad = document.getElementById('ad') ad.addEventListener("click", function(){ //保存 this 的值 // let _this = this; setTimeout(() => { // console.log(this); // _this.style.background = 'pink'; this.style.background = 'pink'; }, 2000); }) </script>
-
方案2:使用 => 箭头函数
复制代码1
2
3
4
5
6
7
8
9// 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调 // 箭头函数不适合与 this 有关的回调. 事件回调, 对象的方法 div.addEventListener('click', function () { setTimeout(() => { console.log(thid); // <div id="ad" style="background: pink;"></div> this.style.backgroundColor = 'pink'; }, 2000); });
7. 函数参数默认值设定
ES6 允许给函数参数设置默认值,当调用函数时不给实参,则使用参数默认值
具有默认值的形参,一般要靠后。
1
2
3let add = (x, y, z=3) => x + y + z; console.log(add(1, 2)); // 6
可与解构赋值结合
1
2
3
4
5
6
7
8
9
10
11
12
13function connect({ host = '127.0.0.1', uesername, password, port }) { console.log(host); // 127.0.0.1 console.log(uesername); console.log(password); console.log(port); } connect({ // host: 'docs.mphy.top', uesername: 'root', password: 'root', port: 3306 })
8. rest 参数
ES6 引入 rest
参数,用于获取函数的实参,用来代替 arguments
,作用与 arguments
类似,将接收的参数序列转换为一个数组对象(arguments
是伪数组)
语法格式:fn(a, b, ...args)
,写在参数列表最后面
应用场景:
rest 参数非常适合不定个数参数函数的场景
1
2
3
4
5
6
7
8
9let fn = (a, b, ...args) => { console.log(a); console.log(b); console.log(args); }; fn(1,2,3,4,5); // 1 2 Array(4)
案例1:求不定个数数字的和
1
2
3
4
5
6let add = (...args) => { let sum = args.reduce((pre, cur) => pre + cur, 0); return sum; } console.log(add(1, 2, 3, 4, 5)); // 15
9. spread 扩展运算符
扩展运算符spread也是三个点...
,它好比 rest 参数的逆运算,将一个数组、伪数组转为用逗号分隔的参数序列,对数组进行解包,扩展运算符也可以将对象解包
可用在调用函数时,传递的实参,将一个数组转换为参数序列(与rest参数的区别,一个用在形参,一个实参)
- 展开数组
1
2
3
4
5
6
7
8
9function fn(a, b, c) { console.log(arguments); console.log(a + b + c); } let arr = ['red', 'green', 'blue']; fn(...arr) // [Arguments] { '0': 'red', '1': 'green', '2': 'blue' } // redgreenblue
- 案例1:数组合并
1
2
3
4
5let A = [1, 2, 3]; let B = [4, 5, 6]; let C = [...A, ...B]; console.log(C); // [1, 2, 3, 4, 5, 6]
- 案例2:数组克隆,这种数组克隆属于浅拷贝
1
2
3
4let arr1 = ['a', 'b', 'c']; let arr2 = [...arr1]; console.log(arr2); // ['a', 'b', 'c']
- 案例3:将伪数组转换为真实数组
1
2
3
4const divs = document.querySelectorAll('div'); let divArr = [...divs]; console.log(divArr);
- 案例4:对象合并
1
2
3
4
5
6
7
8
9
10
11
12
13let obj1 = { a: 123 }; let obj2 = { b: 456 }; let obj3 = { c: 789 }; let obj = { ...obj1, ...obj2, ...obj3 }; console.log(obj); // { a: 123, b: 456, c: 789 }
10. Symbol
10.1 Symbol 基本介绍与使用
JavaScript 的七种基本数据类型
-
值类型(基本类型):string、number、boolean、undefined、null、symbol
-
引用数据类型:object(包括 array、function)
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型
Symbol 的特点
-
Symbol 的值是唯一的,用来解决命名冲突的问题
-
Symbol 值不能与其他数据进行运算
-
Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
Symbol 的创建
-
创建 Symbol()创建
-
使用 Symbol.for() 方法创建,名字相同的 Symbol 具有相同的实体
-
输出 Symbol 变量的描述,使用
description
属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// Symbol() 创建 let s = Symbol(); console.log(s, typeof s); let s2 = Symbol('cess'); let s3 = Symbol('cess'); console.log(s2 === s3); // false // Symbol.for 创建 let s4 = Symbol.for('cess'); let s5 = Symbol.for('cess'); console.log(s2 === s3); // true // 不能与其他数据进行运算 let result = s + 100; let result = s > 100; let result = s + s; let f = Symbol('测试'); console.log(f.description); // 测试
10.2 对象添加 Symbol 类型的属性
案例:安全的向对象中添加属性和方法。
分析:如果直接向对象中添加属性或方法,则原来对象中可能已经存在了同名属性或方法,会覆盖掉原来的。所以使用 Symbol 生成唯一的属性或方法名,可以更加安全的添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 这是一个 game 对象,假设我们不知道里面有什么属性和方法 const game = { uname: '俄罗斯方块', up: function () { }, down: function () { } } // 通过 Symbol 生成唯一的属性名,然后给 game 添加方法 let [up, down] = [Symbol('up'), Symbol('down')]; game[up] = function () { console.log('up'); } game[down] = function () { console.log('down'); } // 调用刚刚创建的方法 game[up](); game[down](); let youxi = { name:"狼人杀", [Symbol('say')]: function(){ console.log("我可以发言") }, [Symbol('zibao')]: function(){ console.log('我可以自爆'); } } console.log(youxi)
10.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 方法判断是否属于这个对象时被调用
1
2
3
4
5
6
7
8
9
10class A { static [Symbol.hasInstance]() { console.log('判断是否属于这个对象时被调用'); } } let obj = {}; console.log(obj instanceof A) // 判断是否属于这个对象时被调用 // false
案例2:数组使用 concat 方法时,是否可以展开
1
2
3
4
5
6
7
8
9let 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 ]
11. 迭代器
11.1 定义
迭代器Iterator是一种接口,为各种不同的数据结构提 供统一的访问机制。任何数据结构只要部署 Iterator
接口(在 js 中接口就是对象的一个属性),就可以完成遍历操作
-
ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费 -
for...in
取的是索引,for...of
取的是 value -
原生具备 iterator 接口的数据(可用 for of 遍历)
-
Array
-
Arguments
-
Set
-
Map
-
String
-
TypedArray
-
NodeList
-
案例:使用 next() 方法遍历原生自带 iterator 接口的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 遍历数组 const xiyou = ['唐僧', '孙悟空', '猪八戒', '沙僧']; //使用 for...of 遍历数组 for (let v of xiyou) { console.log(v); } let iterator = xiyou[Symbol.iterator](); //调用对象的next方法 console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); // 遍历 Map const map = new Map(); map.set('a', 1); map.set('b', 2); map.set('c', 3); let iter = map[Symbol.iterator](); // next() 方法每执行一次,指针自增 console.log(iter.next()); // { value: [ 'a', 1 ], done: false } console.log(iter.next()); // { value: [ 'b', 2 ], done: false } console.log(iter.next()); // { value: [ 'c', 3 ], done: false } console.log(iter.next()); // { value: undefined, done: true }
上面的案例只是为了证明这些数据类型自带 iterator 接口,实际上直接使用 for...of
方法即可完成,使用 for [k, v] of map
来遍历 Map 数据结构中的键和值
1
2
3
4
5
6
7
8
9
10
11
12
13
14const map = new Map(); map.set('a', 1); map.set('b', 2); map.set('c', 3); for (let [k, v] of map) { console.log(k, v); } // 结果 a 1 b 2 c 3
11.2 工作原理
1 创建一个指针对象,指向当前数据结构的起始位置
2 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
3 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
4 每调用 next 方法返回一个包含 value 和 done 属性的对象
应用场景:需要自定义遍历数据的时候,要想到迭代器
11.3 自定义遍历数据
我们可以通过给数据结构添加自定义 [Symbol.iterator]()
方法来使该数据结构能够直接被遍历,从而使 for...of
能够直接遍历指定数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const banji = { name: "终极一班", stus: ['xiaoming', 'xiaoning', 'xiaotian', 'knight'], [Symbol.iterator]() { let index = 0; // 索引变量 let _this = this; return { next: function () { if (index < _this.stus.length) { const result = { value: _this.stus[index], done: false }; index++; // 下标自增 return result; // 返回结果 } else { return {value: undefined, done: true}; } } }; } } // 遍历这个对象 for (let v of banji) { console.log(v); }
12. Generator 生成器函数
12.1 生成器函数的声明和调用
生成器函数是 ES6 提供的一种 异步编程解决方案,语法行为与传统采用纯回调函数完全不同
一般函数从开始运行到它结束之前,不会被任何事情打断。而生成器可以在执行当中暂停自身,可以立即恢复执行,也可以过一段时间之后恢复执行,所以生成器它不能像普通函数那样保证运行到完毕
生成器在每次 暂停/恢复 都提供了一个双向传递信息的功能,生成器可以返回一个值,恢复它的控制代码也可以接收一个值。
- 使用
function * generator()
和yield
可以声明一个生成器函数,* 的位置随意
1
2
3
4
5function * generator(){}; function *generator(){}; function* generator(){}; function*generator(){};
- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next() 方法可以得到 yield 语句后的值
- 每一个 yield 相当于函数的暂停标记,类似 return 要返回值,但不退出函数,每调用一次 next(),生成器函数就往下执行一段
- next 方法可以传递实参,作为 yield 语句的返回值
如下生成器函数中,3 个 yield 语句将函数内部分成了 4 段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 生成器其实就是一个特殊的函数 // 步编程 纯回调函数 node fs ajax mongodb // yield 暂停标记 function * gen(){ // console.log(111); yield '一只没有耳朵'; // console.log(222); yield '一只没有尾部'; // console.log(333); yield '真奇怪'; // console.log(444); } let iterator = gen(); console.log(iterator.next()); // 每调一次,执行一段,并返回yield后面的值 console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); // 遍历 for(let v of gen()) console.log(v); }
12.2 生成器函数的参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function * gen(arg){ console.log(arg); let one = yield 111; console.log(one); let two = yield 222; console.log(two); let three = yield 333; console.log(three); } let iter = generator('AAA'); // 传给生成器第 1 段 console.log(iter.next()); console.log(iter.next('BBB')); console.log(iter.next('CCC')); console.log(iter.next('DDD')); // 结果 AAA Object{value: 111, done: false} BBB Object{value: 222, done: false} CCC Object{value: 333, done: false} DDD Object{value: undefined, done: true}
2.3 生成器函数案例
案例1:1s后输出111,2s后输出222,3s后输出333
- 传统方式:嵌套太多,代码复杂,产生 回调地狱
1
2
3
4
5
6
7
8
9
10setTimeout(() => { console.log(111); setTimeout(() => { console.log(222); setTimeout(() => { console.log(333); }, 3000); }, 2000); }, 1000);
- 生成器实现:结构简洁明了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function 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();
- 案例2:生成器函数模拟每隔1s获取商品数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33function getUsers() { setTimeout(() => { let data = '用户数据'; iter.next(data); // 传参给生成器函数的第 2 段,后面类似 }, 1000); } function getOrders() { setTimeout(() => { let data = '订单数据'; iter.next(data); }, 1000); } function getGoods() { setTimeout(() => { let data = '商品数据'; iter.next(data); }, 1000); } function * generator() { let users = yield getUsers(); console.log(users); let orders = yield getOrders(); console.log(orders); let goods = yield getGoods(); console.log(goods); } let iter = generator(); iter.next();
13. Promise
13.1 Promise 的定义和使用
Promise
是 ES6 引入的异步编程的新解决方案,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果
一个 Promise 必然处于以下几种状态之一
-
待定 pending:初始状态,既没有被兑现,也没有被拒绝
-
已兑现 fulfilled:意味着操作成功完成
-
已拒绝 rejected:意味着操作失败
Promise 的使用
- Promise 构造函数
new Promise((resolve, reject) => {})
Promise.prototype.then()
方法Promise.prototype.catch()
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let p = new Promise(function (resolve, reject) { // 使用 setTimeout 模拟请求数据库数据操作 setTimeout(function () { let isRight = true; // 这个异步请求数据库数据操作是否正确返回数据 if (isRight) { let data = '数据库中的数据'; resolve(data); // 设置 Promise 对象的状态为操作成功 } else { let err = '数据读取失败!' reject(err); // 设置 Promise 对象的状态为操作失败 } }, 1000); }); p.then(function (value) { console.log(value); }, function (reason) { console.error(reason); })
13.2 Promise 封装读取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 使用 nodejs 的 fs 读取文件模块 const fs = require('fs'); const p = new Promise(function (resolve, reject) { fs.readFile('./resources/为学.txt', (err, data) => { if (err) reject(err); // err 是一个异常对象 resolve(data); }) }) p.then(function (value) { console.log(value.toString()); // 转为字符串输出 }, function (reason) { console.log('读取失败!!'); })
13.3 Promise 封装 Ajax 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('get', 'https://api.apiopen.top/getJoke'); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { 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); })
13.4 Promise.prototype.then 方法
Promise.prototype.then 方法返回的结果依然是 Promise 对象,对象状态由回调函数的执行结果决定
具体情况如下
- 若 then 方法没写写返回值,则 then 方法返回的对象的状态值为成功 fulfilled,返回结果值为 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const 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);
RES
PROMISE:{:'出错啦!!!
[[PROTOTYPE]]: PROMISE
[[PROMISESTATE]]: “FULFILLED”
[[PROMISERESULT]]:"出错啦!
如果回调函数中返回的结果是 Promise 类型 return new Promise(),则 then 方法返回的 Promise 对象状态与该返回结果的状态相同,返回值也相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const 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 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14const 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);
13.4 链式调用
Promise.prototype.then 方法返回的结果还是 Promise 对象,这意味着我们可以继续在该结果上使用 then 方法,也就是链式调用,杜绝回调地狱
1
2
3
4
5
6const p = new Promise(resolve=>{}, reject=>{}); p.then(value=>{}, reason=>{}) .then(value=>{}, reason=>{}) .then(value=>{}, reason=>{}) ...
13.5 Promise.prototype.catch
catch() 方法返回一个 Promise,并且处理拒绝的情况
它的行为与调用 Promise.prototype.then(undefined, onRejected)
相同
1
2
3
4obj.catch(onRejected); 等同于 obj.then(undefined, onRejected);
语法
1
2
3
4
5
6p.catch(onRejected); p.catch(function(reason) { // 拒绝 });
举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var 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 捕获异常,这句将不会输出'); }); // 结果 Success oh, no! 有 catch 捕获异常,所以这句输出
13.6 链式调用练习-多个文件读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38const fs = require('fs'); // 回调方式 fs.readFile('./resources/为学.md', (err, data1)=>{ fs.readFile('./resources/插秧诗.md', (err, data2)=>{ fs.readFile('./resources/观书有感.md', (err, data3)=>{ let result = data1 + 'rn' +data2 +'rn'+ data3; console.log(result); }); }); }); // promise 方式 new Promise((resolve, reject) => { fs.readFile('./resources/users.md', (err, data) => { resolve(data); // 设置状态 }) }).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('rn')); });
14. Set
ES6 提供了新的数据结构Set(集合),它类似于数组,但 成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用扩展运算符 … 和 for…of 进行遍历
属性和方法
- st.size:返回集合个数
- st.add(item):往集合中添加一个新元素 item,返回当前集合
- st.delete(item):删除集合中的元素,返回 boolean 值
- st.has(item):检测集合中是否包含某个元素,返回 boolean 值
- st.clear():清空集合
- 集合转为数组:[…st]
- 合并两个集合:[…st1, …st2]
案例1: 数组去重
1
2
3
4let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2]; let res1 = [...new Set(arr1)]; console.log(res1); // [ 1, 2, 3, 4 ]
案例2:数组求交集
1
2
3
4
5let 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:数组求并集
1
2
3
4
5let 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 ]
案例4:数组求差集
1
2
3
4
5let 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 ]
15. Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是 “键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用扩展运算符 … 和 for…of 进行遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14let mp1 = new Map(); mp1.set('aaa', 111); mp1.set('bbb', 222); mp1.set('ccc', 333); let mp2 = new Map([ ['aaa', 111], ['bbb', 222], ['ccc', 333] ]); console.log(mp1['aaa']); // 111 console.log(mp2.get('bbb')); // 222
Map 的属性和方法:(k 为键,v为值)
- size:返回 Map 的元素(键值对)个数
- set(k, v):增加一个键值对,返回当前 Map
- get(k):返回键值对的键值
- has():检测 Map 中是否包含某个元素
- clear():清空集合,返回 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30let m = new Map(); //添加元素 m.set('name','school'); m.set('change', function(){ console.log("我们可以改变你!!"); }); let key = { school : 'ATSCHOOL' }; m.set(key, ['北京','上海','深圳']); //size 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); }
16. class 类
可跳转 JS 高阶 class
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
16.1 复习 ES5 function 构造函数 的继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 手机 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; 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);
16.2 extends 类继承和方法的重写
ES6 中直接使用 extends 语法糖(更简洁高级的实现方式)来实现继承,同时可以重写父类的方法,直接在子类中重新写一次要重写的方法即可覆盖父类方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class 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("拍照"); } playGame() { console.log("玩游戏"); } // 子类重写父类方法,super.call()可以调父类方法 call() { console.log('我可以进行视频通话'); } } const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch'); // console.log(xiaomi); xiaomi.call(); xiaomi.photo(); xiaomi.playGame();
16.3 静态成员
函数对象的属性 方法是属于函数对象的,其创建的实例对象无法使用,这就是静态成员
1
2
3
4
5
6
7
8
9
10
11
12function Phone(){} Phone.name = '手机'; Phone.change = function(){ console.log("我可以改变世界"); } Phone.prototype.size = '5.5inch'; let nokia = new Phone(); // console.log(nokia.name); // nokia.change(); console.log(nokia.size);
1
2
3
4
5
6
7
8
9
10
11
12class Phone{ //静态属性 static name = '手机'; static change(){ console.log("我可以改变世界"); } } let nokia = new Phone(); console.log(nokia.name); // undefined console.log(Phone.name);
16.4 getter 和 setter
实际上,getter和setter是 ES5(ES2009)提出的特性 当属性拥有 get/set 特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter/setter 函数
使用场景: getter 是一种语法,这种 get 将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter 则是在修改某一属性时所给出的相关提示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// get 和 set class Phone { get price() { console.log("价格属性被读取了"); return 'iloveyou'; } set price(newVal) { console.log('价格属性被修改了'); } } //实例化对象 let s = new Phone(); // console.log(s.price); s.price = 'free';
17. 数值扩展
Number.EPSILON
是 JavaScript 表示的最小精度,一般用来处理浮点数运算。例如可以用于两个浮点数的比较
1
2
3
4
5let 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
- 二进制和八进制:二进制以
0b
开头,八进制以0o
开头
1
2
3
4
5
6
7let b = 0b1010; let o = 0o777; let d = 100; let x = 0xff; console.log(x);
-
Number.isFinite
检测一个数值是否为有限数。复制代码1
2
3
4console.log(Number.isFinite(100)); // true console.log(Number.isFinite(100 / 0)); // false console.log(Number.isFinite(Infinity)); // false
-
Number.parseInt
和Number.parseFloat
ES6 给 Number 添加了 parseInt 方法,Number.parseInt
完全等同于parseInt
。将字符串转为整数,或者进行进制转换。Number.parseFloat
则等同于parseFloat()
1
2Number.parseInt(s, base);
- s:待转换的字符串
- base:进位制的基数
1
2
3
4
5
6Number.parseInt === parseInt; // true Number.parseFloat === parseFloat; // true console.log(Number.parseInt('5211314love')); // 5211314 console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
Number.isInteger()
判断一个数是否为整数Math.trunc()
将数字的小数部分抹掉Math.sign
判断一个数到底为正数 负数 还是零Number.isNaN()
检测一个数值是否为 NaN
1
2
3
4
5
6
7
8
9console.log(Number.isInteger(5)); // true console.log(Number.isInteger(2.5)); // false console.log(Math.trunc(3.5)); // 3 console.log(Math.sign(100)); // 1 console.log(Math.sign(0)); // 0 console.log(Math.sign(-20000)); // -1
18. 对象方法扩展
ES6 新增了一些 Object 对象的方法
18.1 Object.is()
Object.is() 方法判断两个值是否完全相同。Object.is 比较两个值是否严格相等,与 === 行为 基本一致,返回一个 Boolean 类型
1
2Object.is(value1, value2);
Object.is() 方法判断两个值是否为同一个值。如果满足以下条件则两个值相等
-
都是 undefined
-
都是 null
-
都是 true 或 false
-
都是相同长度的字符串且相同字符按相同顺序排列
-
都是相同对象(意味着每个对象有同一个引用)
-
都是数字且
○ 都是 +0
○ 都是 -0
○ 都是 NaN
○ 或都是非零而且非 NaN 且为同一个值
与 == 运算不同。 == 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换(这种行为的结果会将 “” == false 判断为 true),而 Object.is 不会强制转换两边的值
与 === 算也有不同,=== 运算符 (也包括 == 运算符) 将数字 -0 和 +0 视为相等,而将 Number.NaN 与 NaN 视为不相等
18.2 Object.assign
Object.assign
对象的合并,相当于浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const 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)); // 有相同属性,后面的会覆盖前面的
18.3 Object.setPrototypeOf 和 Object.getPrototypeof
Object.setPrototypeOf
用于设置对象的原型对象
Object.getPrototypeof
用于获取对象的原型对象,相当于 proto
1
2
3
4
5
6
7
8
9
10const school = { name: '尚硅谷' } const cities = { xiaoqu: ['北京','上海','深圳'] } Object.setPrototypeOf(school, cities); // 将school的隐式原型设置为cities console.log(Object.getPrototypeOf(school)); console.log(school);
19. ES6 模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
19.1 模块化的好处
模块化的优势有以下几点
-
防止命名冲突
-
代码复用
-
高维护性
19.2 模块化规范产品
ES6 之前的模块化规范有(左侧规范,右侧产品)
-
CommonJS => NodeJS、Browserify
-
AMD => requireJS
-
CMD => seaJS
19.3 ES6 模块化语法
模块功能主要由两个命令构成
-
export 命令用于规定模块的对外接口
-
import 命令用于输入其他模块提供的功能
1
2
3
4
5
6
7// m1.js expert let school = 'w3c'; expert function teach() { console.log("我们可以教给你开发技能"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ES6 模块化</title> </head> <body> <script type="module"> import * as m1 from "./src/js/m1.js"; console.log(m1) </script> </body> </html>
1 模块导出数据语法
1 分别暴露
1
2
3
4
5
6export let school = 'w3c'; export function teach() { console.log("我们可以教给你开发技能"); }
2 统一暴露
1
2
3
4
5
6
7let school = 'w3c'; function findJob(){ console.log("我们可以帮助你找工作!!"); } export {school, findJob}
3 默认暴露
1
2
3
4
5
6
7export default { school: 'w3c', change: function(){ console.log("我们可以改变你!!"); } }
2 模块导入数据语法
1 通用导入方式
1
2
3
4
5
6import * as m1 from './js/m1.js'; import * as m2 from './js/m2.js'; import * as m3 from './js/m3.js'; m3.default.change()
2 解构赋值导入
1
2
3
4import {school, teach} from "./src/js/m1.js"; import {school as guigu, findJob} from "./src/js/m2.js"; import {default as m3} from "./src/js/m3.js";
3. 简便方式导入,只能用于默认暴露
1
2import m3 from "./src/js/m3.js";
3 ES6 使用模块化方式二
将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 入口文件 // 模块引入 import * as m1 from "./m1.js"; import * as m2 from "./m2.js"; import * as m3 from "./m3.js"; // console.log(m1); // console.log(m2); // console.log(m3); // m1.teach(); // m2.findJob(); // m3.default.change(); //修改背景颜色为粉色 import $ from 'jquery';// const $ = require("jquery"); $('body').css('background','pink');
在 index.html 中引入 app.js 文件内容:
1
2<script src="./src/js/app.js" type="module"></script>
19.4 使用 babel 对模块化代码转换
有的浏览器不支持 ES6 语法,这时候就需要使用 babel 来将其转换成 ES5 等价语法
1. 安装工具
babel-cli babel命令行工具
babel-preset-env babel预设包,支持新的ES特性,转换为ES5语法
browserify/webpack 打包工具
1
2npm i babel-cli babel-preset-env browserify(webpack) -D
2. 编译
1
2
3
4// 局部安装使用 -d 目标目录 npx babel src/js -d dist/js --presets=babel-preset-env // 全局安装可以省略 npx
3 打包
1
2
3// -o 输出到的位置 npx browserify dist/js/app.js -o dist/bundle.js
此时再引入
1
2<script src="dist/bundle.js"></script>
19.5 ES6 模块化引入 npm 安装的包
1
2npm install jquery
再通过 import
导入即可
1
2import $ from 'jquery'
三 ECMASript 7 新特性
1. Array.prototype.includes
includes 方法用来检测数组中是否包含某个元素,返回布尔类型值
2. 指数运算符
在 ES7 中引入指数运算符 **,用来实现幂运算,功能与 Math.pow(a, b)
结果相同
1
2
3
4
5
6
7
8
9
10
11// includes indexOf也可以判断 const mingzhu = ['西游记','红楼梦','三国演义','水浒传']; //判断 console.log(mingzhu.includes('西游记')); // console.log(mingzhu.includes('金瓶梅')); // ** console.log(2 ** 10); // 1024 console.log(Math.pow(2, 10));
四 ECMAScript 8 新特性
1. async 和 await
async 和 await 两种语法结合可以让异步代码像同步代码一样(看起来是同步的,实质上是异步的)
先从字面意思理解,async 意为异步,可以用于声明一个函数前,该函数是异步的。await 意为等待,即等待一个异步方法完成
1.1 async
async 将 function 变为成为 async 函数
async
内部可以使用await
,也可以不使用,因此执行这个函数时,可以使用 then 和 catch 方法async
函数的返回值是一个Promise
对象Promise
对象的结果由 async 函数执行的返回值决定
1
2
3
4async function funcName() { //statements }
- 函数体不 return 返回值,则 async 函数返回值为一个成功 fulfilled 的 Promise 对象,值为 undefined
1
2
3
4let a = async function() {} let res = a() console.log(res) // Promise{<fullfilled>: undefined}
- return 结果不是一个 Promise ,则 async 函数返回值为一个成功 fulfilled 的 Promise 对象,状态值为这个内部返回值
1
2
3
4
5
6let a = async function () { return 'hello' } let res = a() console.log(res) // Promise{<fullfilled>: 'hello'}
- 内部抛出错误,则 async 函数返回值为一个失败 reject 的 Promise 对象
1
2
3
4
5
6
7let a = async function foo() { throw new Error('出错了') } a().catch(reason => { console.log(reason) })
- 若函数内部返回值是一个 Promise 对象,则 async 函数返回值的状态取决于这个 Promise 对象 是resolve 还是 reject
1
2
3
4
5
6
7
8
9let a = async function () { return new Promise((resolve, reject) => { resolve("成功") }) } a().then(value => { console.log(value) })
1.2 await
await 相当于一个运算符,右边接一个值。一般为一个 Promise 对象,也可以是一个非 Promise 类型。
- 当右接一个非 promise 类型,await 表达式返回的值就是这个值;
- 当右接一个 promise 对象,则 await 表达式会阻塞后面的代码,等待当前 promise 对象 resolve 的值
- 综合 async 和 await 而言
- await 必须写在 async 函数中
- await 右侧的表达式一般为 promise 对象
- await 返回的是 promise 成功的值
- await 的 promise 失败了就会抛出异常,需要使用 try-catch 捕获处理
Promise 使用链式调用解决了传统方式回调地狱的问题,而 async-await 又进一步优化了代码的可读性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 创建 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(); // '失败'
1.3 综合应用-读取文件
需求:先后读取三个md文件
对于这种异步操作很容易想到使用 Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34const 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 方法优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44//1. 引入 fs 模块 const fs = require("fs"); //读取『为学』 function readWeiXue() { return new Promise((resolve, reject) => { fs.readFile("./resources/为学.md", (err, data) => { if (err) reject(err); resolve(data); }) }) } function readChaYangShi() { return new Promise((resolve, reject) => { fs.readFile("./resources/插秧诗.md", (err, data) => { if (err) reject(err); resolve(data); }) }) } function readGuanShu() { return new Promise((resolve, reject) => { fs.readFile("./resources/观书有感.md", (err, data) => { if (err) reject(err); resolve(data); }) }) } //声明一个 async 函数 async function main(){ let weixue = await readWeiXue(); // 获取为学内容 let chayang = await readChaYangShi(); // 获取插秧诗内容 let guanshu = await readGuanShu(); // 获取观书有感 console.log(weixue.toString()); console.log(chayang.toString()); console.log(guanshu.toString()); } main()
1.4 综合应用-封装ajax
axios 返回的就是一个 promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 发送 AJAX 请求, 返回的结果是 Promise 对象 function sendAJAX(url) { return new Promise((resolve, reject) => { const x = new XMLHttpRequest(); x.open('GET', url); x.send(); x.onreadystatechange = function () { if (x.readyState === 4) { if (x.status >= 200 && x.status < 300) { resolve(x.response); } reject(x.status); } } }) } //promise then 方法测试 // sendAJAX("https://api.apiopen.top/getJoke").then(value=>{ // console.log(value); // }, reason=>{}) // async 与 await 测试 axios async function main(){ //发送 AJAX 请求 let result = await sendAJAX("https://api.apiopen.top/getJoke"); let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P') console.log(tianqi); } main();
2. Object.values 和 Object.entries
- Object.values() 方法返回一个给定对象的所有可枚举属性值的数组
- Object.keys(),只是前者返回属性值,后者返回键值组合的数组
- Object.entries() 方法返回一个给定对象自身可遍历属性 [key,value] 的数组,可以使用 for…of 遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let obj = { a: 1, b: {1:2}, c: [1,2,3] } console.log(Object.values(obj)) // [1, {1: 2}, [1,2,3]] console.log(Object.keys(obj)) // ['a', 'b', 'c'] const obj = {a: 1, b: 2, c: 3}; console.log(Object.entries(obj)) // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ] for (let [k, v] of Object.entries(obj)) { console.log(k, v) }
- Object.getOwnPropertyDescriptors() 获取对象属性的描述对象
1
2
3
4
5
6
7
8
9
10
11
12
13console.log(Object.getOwnPropertyDescriptors(school)); const obj = Object.create(null, { name: { //设置值 value: 'school', //属性特性 writable: true, configurable: true, enumerable: true } });
最后
以上就是落寞纸飞机最近收集整理的关于JavaScript 精选:哪些能提高开发效率的es6 新语法糖一 ECMAScript 相关介绍二 ECMASript 6 新特性三 ECMASript 7 新特性四 ECMAScript 8 新特性的全部内容,更多相关JavaScript内容请搜索靠谱客的其他文章。
发表评论 取消回复