本次的ES6语法的汇总总共分为上、中、下三篇,本篇文章为下篇。
往期系列文章:
客套话不多说了,直奔下篇的内容~
async函数
ES2017标准引入了async
函数,使得异步操作更加方便。async
函数是Generator
函数的语法糖。不打算写Generator函数,感兴趣的话可以看文档。与Generator
返回值(Iterator对象)不同,async
返回的是一个Promise
对象。
用法
async
函数返回一个Promise对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function(result) { console.log(result); }) 复制代码
再来看几种情况加深下印象:
function fun1() { console.log('fun1'); return 'fun1 result'; } async function test() { const result1 = await fun1(); console.log(result1); console.log('end'); } test(); // 输出 // 'fun1' // 'fun1 result' // 'end' 复制代码
async function fun2() { console.log('fun2'); return 'fun2 result'; } async function test() { const result2 = await fun2(); console.log(result2); console.log('end'); } test(); // 输出 // 'fun2' // 'fun2 result' // 'end' 复制代码
正常情况下,await
命令后面是一个Promise
对象,返回该对象的结果。如果不是Promise
对象,就直接返回对应的值。
async function fun3() { console.log('fun3'); setTimeout(function() { console.log('fun3 async'); return 'fun3 result'; }, 1000) } async function test() { const result3 = await fun3(); console.log(result3); console.log('end'); } test(); // 输出 // 'fun3' // undefined // 'end' // 'fun3 async' 复制代码
async function fun4() { console.log('fun4'); return new Promise((resolve, reject) => { setTimeout(() => { console.log('fun4 async'); resolve('fun4 result'); }, 1000); }) } async function test() { console.log(result4); console.log('fun4 sync'); console.log('end'); } test(); // 输出 // 'fun4' // 'fun4 async' // 'fun4 result' // 'fun4 sync' // 'end' 复制代码
模拟sleep
JavaScript
一直没有休眠的语法,但是借助await
命令就可以让程序停顿指定的时间。【await要配合async来实现】
function sleep(interval) { return new Promise(resolve => { setTimeout(resolve, interval); }) } // use async function one2FiveInAsync() { for(let i = 1; i <= 5; i++) { console.log(i); await sleep(1000); } } one2FiveInAsync(); // 1, 2, 3, 4, 5 每隔一秒输出数字 复制代码
一道题
需求:使用async await
改写下面的代码,使得输出的期望结果是每隔一秒输出0, 1, 2, 3, 4, 5
,其中i < 5
条件不能变。
for(var i = 0 ; i < 5; i++){ setTimeout(function(){ console.log(i); },1000) } console.log(i); 复制代码
之前我们讲过了用promise的方式实现,这次我们用async await
方式来实现:
const sleep = (time) => new Promise((resolve) => { setTimeout(resolve, time); }); (async () => { for(var i = 0; i < 5; i++){ console.log(i); await sleep(1000); } console.log(i); })(); // 符合条件的输出 0, 1, 2, 3, 4, 5 复制代码
比较promise和async
为什么只比较promise
和async
呢?因为这两个用得频繁,实在的才是需要的,而且async语法
是generator
的语法糖,generator
的说法直接戳async与其他异步处理方法的比较。
两者上,async语法
写法上代码量少,错误处理能力佳,而且更有逻辑语义化。
假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。
// promise function chainAnimationsPromise(elem, animations) { // 变量ret用来保存上一个动画的返回值 let ret = null; // 新建一个空的Promise let p = Promise.resolve(); // 使用then方法,添加所有动画 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一个部署了错误捕捉机制的Promise return p.catch(function(e) { /* 忽略错误,继续执行 */ }).then(function() { return ret; }); } 复制代码
// async await async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; } 复制代码
类class
在ES6
之前,是使用构造函数来模拟类的,现在有了关键字class
了,甚是开心😄
function Person() {} Person.prototype.sayHello = function(){ console.log('Hi'); }; 复制代码
class Person{ sayHello(){ console.log('Hi!'); } } 复制代码
constructor方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法,一个类中必须有construtor
方法,如果没有显式定义,一个空的constructor
方法会默认添加。
class Person{} // 等同于 class Person{ constructor(){} } 复制代码
construtor
方法也就类似构造函数,在执行new的时候,先跑构造函数,再跑到原型对象上。
取值函数(getter)和存值函数(setter)
与ES5一样,在类的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass { get prop() { return 'getter'; } set prop(value) { console.log(`setter: ${ value }`) } } let inst = new MyClass(); inst.prop = 123; // 'setter: 123' console.log(inst.prop); // 'getter' 复制代码
this的指向
类的方法内部如果含有this
,它默认是指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Person{ constructor(job) { this.job = job; } printJob() { console.log(`My job is ${ this.job }`); } sayHi() { console.log(`I love my job -- ${ this.job }.`) } } const person = new Person('teacher'); person.printJob(); // 'My job is teacher' const { sayHi } = person; sayHi(); // 报错: Uncaught TypeError: Cannot read property 'job' of undefined 复制代码
上面的代码中,sayHi
方法单独使用,this
会指向该方法运行时所在的环境(由于class内部是严格模式,所以this
实际上指向undefined
)。
修正上面的错误也很简单,也是我们在react
开发中经常使用的一种手段:在调用构造函数实例化的时候直接绑定实例(this),修改如下:
class Person{ constructor(job) { this.job = job; this.sayHi = this.sayHi.bind(this); } } 复制代码
继承
ES5中继承的方式我之前有整理过--JavaScript 中的六种继承方式。
ES6中的继承通过extends
关键字实现,比ES5的实现继承更加清晰和方便了。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; } } let cp = new ColorPoint(25, 8, 'green'); // 报错: Must call super constructor in derived class before accessing 'this' or returning from derived constructor 复制代码
上面这样写,不能继承构造函数里面的属性值和方法。需要在子类的构造函数中加上super
关键字。改成下面这样即可:
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的construtor(x, y),相当于ES5中的call。注意的是,super要放在子类构造函数的第一行 this.color = color; } } let cp = new ColorPoint(25, 8, 'green'); 复制代码
module模块
在ES6之前,社区制定了一些模块的加载的方案,最主要的有CommonJS
和AMD
两种。前者用于服务器,后者用于浏览器。
// CommonJS let { stat, exists, readFile } = require('fs'); 复制代码
ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS
和AMD
规范,成为浏览器和服务器通用的模块解决方案。
// ES6模块 import { stat, exists, readFile } from 'fs'; 复制代码
各种好处详细见文档
export命令
export
命令用于规定模块的对外接口。
**一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。**你可以理解为一个命名空间~
想要获取模块里面的变量,你就需要导出export
:
// profile.js const name = 'jia ming'; const sayHi = function() { console.log('Hi!'); } export { name, sayHi }; 复制代码
还有一个export default
命令,方便用户(开发者啦)不用阅读文档就能加载模块(实际上就是输出一个default
变量,而这个变量在import
的时候是可以更改的):
// export-default.js export default function () { console.log('foo'); } 复制代码
其他模块加载该模块时,import
命令可以为该匿名函数指定任意名字。
// import-default.js import customName from './export-default'; customName(); // 'foo' 复制代码
import命令
import
命令用于输入其他模块提供的功能。使用export
命令定义了模块的对外接口以后,其他JS
文件就可以通过import
命令加载这个模块。
// main.js import { name, sayHi } from './profile.js'; function printName() { console.log('My name is ' + name); } 复制代码
至此,本系列文章谈谈ES6语法已经写完,希望文章对读者有点点帮助。本系列的内容是个人觉得在开发中比较重要的知识点,如果要详细内容的话,请上相关的文档查看~💨