本次的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語法已經寫完,希望文章對讀者有點點幫助。本系列的內容是個人覺得在開發中比較重要的知識點,如果要詳細內容的話,請上相關的文件檢視~?
參考和後話
本次的ES6語法的彙總總共分為上、中、下三篇,本篇文章為下篇。
系列文章至此已經完結!
文章首發在github上--談談ES6語法(彙總下篇)。更多的內容,請戳我的部落格進行了解,能留個star就更好了?