本次的ES6語法的匯總總共分為上、中、下三篇,本篇文章為下篇。
往期系列文章:
客套話不多說了,直奔下篇的內(nèi)容~
async函數(shù)
ES2017標準引入了async函數(shù),使得異步操作更加方便。async函數(shù)是Generator函數(shù)的語法糖。不打算寫Generator函數(shù),感興趣的話可以看文檔。與Generator返回值(Iterator對象)不同,async返回的是一個Promise對象。
用法
async函數(shù)返回一個Promise對象,可以使用then方法添加回調(diào)函數(shù)。當函數(shù)執(zhí)行的時候,一旦遇到await就會先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。
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對象,返回該對象的結(jié)果。如果不是Promise對象,就直接返回對應(yīng)的值。
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命令就可以讓程序停頓指定的時間?!綼wait要配合async來實現(xiàn)】
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 每隔一秒輸出數(shù)字
一道題
需求:使用async await改寫下面的代碼,使得輸出的期望結(jié)果是每隔一秒輸出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的方式實現(xiàn),這次我們用async await方式來實現(xiàn):
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 元素上面,部署了一系列的動畫,前一個動畫結(jié)束,才能開始后一個。如果當中有一個動畫出錯,就不再往下執(zhí)行,返回上一個成功執(zhí)行的動畫的返回值。
// 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) {
/* 忽略錯誤,繼續(xù)執(zhí)行 */
}).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) {
/* 忽略錯誤,繼續(xù)執(zhí)行 */
}
return ret;
}
類class
在ES6之前,是使用構(gòu)造函數(shù)來模擬類的,現(xiàn)在有了關(guān)鍵字class了,甚是開心??
function Person() {}
Person.prototype.sayHello = function(){
console.log('Hi');
};
class Person{
sayHello(){
console.log('Hi!');
}
}
constructor方法
constructor方法是類的默認方法,通過new命令生成對象實例時,自動調(diào)用該方法,一個類中必須有construtor方法,如果沒有顯式定義,一個空的constructor方法會默認添加。
class Person{}
// 等同于
class Person{
constructor(){}
}
construtor方法也就類似構(gòu)造函數(shù),在執(zhí)行new的時候,先跑構(gòu)造函數(shù),再跑到原型對象上。
取值函數(shù)(getter)和存值函數(shù)(setter)
與ES5一樣,在類的內(nèi)部可以使用get和set關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。
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的指向
類的方法內(nèi)部如果含有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會指向該方法運行時所在的環(huán)境(由于class內(nèi)部是嚴格模式,所以this實際上指向undefined)。
修正上面的錯誤也很簡單,也是我們在react開發(fā)中經(jīng)常使用的一種手段:在調(diào)用構(gòu)造函數(shù)實例化的時候直接綁定實例(this),修改如下:
class Person{
constructor(job) {
this.job = job;
this.sayHi = this.sayHi.bind(this);
}
}
繼承
ES5中繼承的方式我之前有整理過--JavaScript 中的六種繼承方式。
ES6中的繼承通過extends關(guān)鍵字實現(xiàn),比ES5的實現(xiàn)繼承更加清晰和方便了。
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
上面這樣寫,不能繼承構(gòu)造函數(shù)里面的屬性值和方法。需要在子類的構(gòu)造函數(shù)中加上super關(guān)鍵字。改成下面這樣即可:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類的construtor(x, y),相當于ES5中的call。注意的是,super要放在子類構(gòu)造函數(shù)的第一行
this.color = color;
}
}
let cp = new ColorPoint(25, 8, 'green');
module模塊
在ES6之前,社區(qū)制定了一些模塊的加載的方案,最主要的有CommonJS和AMD兩種。前者用于服務(wù)器,后者用于瀏覽器。
// CommonJS
let { stat, exists, readFile } = require('fs');
ES6在語言標準的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當簡單,完全可以取代 CommonJS和AMD規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。
// ES6模塊
import { stat, exists, readFile } from 'fs';
各種好處詳細見文檔
export命令
export命令用于規(guī)定模塊的對外接口 。
一個模塊就是一個獨立的文件。該文件內(nèi)部的所有變量,外部無法獲取。你可以理解為一個命名空間~
想要獲取模塊里面的變量,你就需要導(dǎo)出export:
// profile.js
const name = 'jia ming';
const sayHi = function() {
console.log('Hi!');
}
export { name, sayHi };
還有一個export default命令,方便用戶(開發(fā)者啦)不用閱讀文檔就能加載模塊(實際上就是輸出一個default變量,而這個變量在import的時候是可以更改的):
// export-default.js
export default function () {
console.log('foo');
}
其他模塊加載該模塊時,import命令可以為該匿名函數(shù)指定任意名字。
// 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);
}
至此,本系列文章談?wù)凟S6語法已經(jīng)寫完,希望文章對讀者有點點幫助。本系列的內(nèi)容是個人覺得在開發(fā)中比較重要的知識點,如果要詳細內(nèi)容的話,請上相關(guān)的文檔查看~??
參考和后話
本次的ES6語法的匯總總共分為上、中、下三篇,本篇文章為下篇。
系列文章至此已經(jīng)完結(jié)!
文章首發(fā)在github上--談?wù)凟S6語法(匯總下篇)。更多的內(nèi)容,請戳我的博客進行了解,能留個star就更好了??