談談ES6語法(彙總下篇)

call_me_R發表於2019-07-14

本次的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

為什麼只比較promiseasync呢?因為這兩個用得頻繁,實在的才是需要的,而且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一樣,在的內部可以使用getset關鍵字,對某個屬性設定存值函式和取值函式,攔截該屬性的存取行為

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之前,社群制定了一些模組的載入的方案,最主要的有CommonJSAMD兩種。前者用於伺服器,後者用於瀏覽器。

// CommonJS
let { stat, exists, readFile } = require('fs');
複製程式碼

ES6在語言標準的層面上,實現了模組功能,而且實現得相當簡單,完全可以取代 CommonJSAMD規範,成為瀏覽器和伺服器通用的模組解決方案。

// 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就更好了?

相關文章