快速入門全棧 - 09-2 ES6(下)

一、運算符與語句

1. 函數

默認參數
我們可以爲函數的參數賦予默認值,當該函數沒有被賦值的時候,會使用默認值

function fn(name,age=17){
	console.log(name +" " + age);
}
fn("Amy"); // Amy 17

需要注意以下幾點:

  • 函數使用默認參數的時候,不能有同名參數
  • 傳遞null被認爲是有效參數,不會使用默認參數的值
  • 未初始化的參數無法成爲默認參數的值

不定參數
使用不定參數,我們可以讓函數在調用的時候傳遞任意個參數值

function f(...values){
	console.log(values.length);
}
f(1,2);      // 2
f(1,2,3,4);  // 4

箭頭函數
箭頭函數提供一種更簡潔書寫函數的方式,語法是 參數 => 函數體,函數體是要返回的結果

var f = v => v
// 等價於
var f = function(a){
	return a;
}

var f = (a,b) => a + b
f(6,2);  // 8

如果函數體有多行,則使用{}包裹起來即可。

2. 迭代器

迭代器是ES6一種新的遍歷機制,目的是使用一個統一的接口來使得各種數據結構易於訪問。使用迭代器的過程如下:

  • 使用Symbol.iterator創建一個迭代器,指向的是當前數據結構的起始位置
  • 通過next方法來獲取下一個元素,next方法會返回當前位置的對象,包含了value和done兩個屬性,value是當前位置的元素,done是是否遍歷結束
  • 當done爲true的時候,迭代結束
const items = [0,1,2];
const it = item[Symbol.iterator]();

it.next();
> {value:0, done:false}
it.next();
> {value:1, done:false}
it.next();
> {value:2, done:false}
it.next();
> {value:undefined, done:true}

迭代器可以迭代的值有:Array、String、Map、Set和Dom元素

3. 類

定義類
在ES6中,類可以作爲對象的模板被引入,通過class關鍵字來定義類。類可以讓對象原型的寫法更清晰,更像面向對象編程的語法。

class Example {
	constructor(a){
		this.a = a;
	}
}

需要注意的是,類不可以重複定義,且類中方法不需要function關鍵字,也不能加分號。類中的方法依然是定義在prototype上面的,因此重寫或添加方法的時候可以寫

Example.prototype = {
	// methods
}
// 或者
Object.assign(Example.prototype, {
	// methods
})

類中可以定義靜態方法,但在ES6中,不支持定義靜態屬性

class Example{
    static sum(a, b) {
        console.log(a+b);
    }
}
Example.sum(1, 2); // 3

實例化
我們使用new關鍵字來實例化一個類,就像其他面向對象編程的語言一樣。

class Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Example');
    }
    sum() {
        return this.a + this.b;
    }
}
let exam1 = new Example(2, 1);

裝飾器
裝飾器是一個函數,用來修改類的行爲。裝飾器可以給類使用,也可以給方法使用。

給類使用的裝飾器要接收一個參數target,是需要修飾的類

function testable(isTestable) {
    return function(target) {
        target.isTestable=isTestable;
    }
}

@testable(true)
class Example {}
Example.isTestable; // true

給方法修飾的裝飾器有三個參數,分別是target(要修飾的方法)、name(修飾的屬性名)、descriptor(屬性的描述對象)

class Example {
    @writable
    sum(a, b) {
        return a + b;
    }
}
function writable(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor; // 必須返回
}

裝飾器執行的時候由外向內進入,由內向外執行

封裝
我們可以在類中定義屬性,再用getter和setter來進行訪問和修改

class Example1{
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    get a(){
        console.log('getter');
        return this._a;
    }
    set a(a){
        console.log('setter');
        this._a = a;
    }
}

注意setter不能寫成this.a = a,這樣寫會無限遞歸最終報出RangeError。getter不可單獨出現,且必須和setter在同級出現(不能一個在父類一個在子類)。

繼承
我們可以通過extends關鍵字來讓一個類繼承一個父類

class Father {
	static test1(){
        return 1;
    }
}
class Child extends Father {
    constructor(){
        super();
    }
    static test3(){
        return super.test1+2;
    }
}

super關鍵字可以訪問到父類對象。在普通方法中,super訪問到的是父類的原型,在靜態方法中,super訪問到的是父類。

4. 模塊

ES6的模塊會自動開啓嚴格模式,可以導入和到處各種類型的變量和對象。每個模塊都有自己的上下文,聲明的變量都是局部變量,不會污染全局;且每個模塊只能加載一次,若加載同目錄下同文件,則直接從內存中讀取。

export和import
我們使用export和import來導入導出各種類型的變量

  • 到處的函數聲明必須要有名稱
  • 不僅能導出聲明還能導出引用
  • export可以出現在模塊的任意位置,但必須處於模塊頂層
  • import會被提升到模塊頭部,首先執行
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
    return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass =  class myClass {
    static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
 
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!

建議使用大括號指定所有輸出的變量集合在文檔尾部。函數與類都需要對應的名稱,到處文檔尾部避免了無對應名稱。其中import關鍵字還有以下幾個需要注意的地方

  • 不允許在加載模塊的腳本中改寫接口的引用指向,即可以改寫import變量類型爲對象的屬性值,不能改寫import變量類型爲基本類型的值
  • 多次重複執行一個import指令,只會執行一次
  • import是靜態執行,不能使用表達式和變量

as
as關鍵字可以將導入的模塊重命名

/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry

export default
在一個文件中,export、import可以有多個,而export default僅有一個。export default的default是對應的導出接口變量;通過export的方式導出,import的時候需要加{},而export default不需要;export default向外暴露的成員,可以使用任意變量來接收。

var a = "My name is Tom!";
export default a; // 僅有一個
export default var c = "error"; 
// error,default 已經是對應的導出變量,不能跟着變量聲明語句
 
import b from "./xxx.js"; // 不需要加{}, 使用任意變量接收

二、異步編程

1. Promise對象

Promise對象是異步編程的一個對象,可以獲取異步操作的消息。

狀態
Promise異步操作有三種狀態:pending、fulfilled和rejected。除了異步操作的結果,其他任何操作都無法改變狀態。狀態有以下幾個缺點:

  • 一旦新建狀態就會立即執行,中途無法取消
  • 如果不設置回調函數,Promise內部的錯誤不會反應到外部
  • 當處於pending狀態的時候,無法得知是剛剛開始還是即將完成

then方法
then方法接收兩個函數作爲參數,一個是Promise執行成功時的回調,一個是Promise執行失敗時的回調,兩個函數只有一個會被調用。在JavaScript事件隊列的當前運行完成之前,回調函數永遠不會被調用。我們可以通過多次.then的形式添加回調函數

const p = new Promise(function(resolve,reject){
  resolve(1);
}).then(function(value){ // 第一個then // 1
  console.log(value);
  return value * 2;
}).then(function(value){ // 第二個then // 2
  console.log(value);
}).then(function(value){ // 第三個then // undefined
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){ // 第四個then // resolve
  console.log(value);
  return Promise.reject('reject'); 
}).then(function(value){ // 第五個then //reject:reject
  console.log('resolve:' + value);
}, function(err) {
  console.log('reject:' + err);
});

使用Promise的時候最好保持扁平化,不要嵌套Promise,注意總時返回或終止Promise鏈。大部分瀏覽器不能終止Promise鏈中的rejection,建議跟上.catch(error => console.log(error));

2. Generator函數

ES6引入了Generator函數,可以通過yield關鍵字將執行流掛起,爲改變執行順序提供了可能性,從而實現異步編程。Generator在定義的時候,在function關鍵字後面要添加*,且函數內部存在yield表達式

function* f(){
	console.log("one");
	yield '1';
	console.log("two");
	yield '2';
	console.log("three");
	return '3';
}

Generator函數在使用的時候和迭代器十分類似,需要使用next()方法來訪問內部狀態。

f.next();
// one
// {value: "1", done: false}
 
f.next();
// two
// {value: "2", done: false}
 
f.next();
// three
// {value: "3", done: true}
 
f.next();
// {value: undefined, done: true}

next()方法可以傳遞參數。如果next()方法不傳遞參數,yield表達式返回值爲undefined,如果next()方法傳遞參數,該參數會作爲上一個yield表達式的返回值。

Generator函數也可以通過yield*來實現嵌套,相當於在一個Generator函數中調用另一個Generator函數

function* callee() {
    console.log('callee: ' + (yield));
}
function* caller() {
    while (true) {
        yield* callee();
    }
}
const callerObj = caller();
callerObj.next();
// {value: undefined, done: false}
callerObj.next("a");
// callee: a
// {value: undefined, done: false}
callerObj.next("b");
// callee: b
// {value: undefined, done: false}
 
// 等同於
function* caller() {
    while (true) {
        for (var value of callee) {
          yield value;
        }
    }
}
3. async關鍵字

async函數是ES7新增的關鍵字,和Promise、Generator有很大關係。async函數會返回一個Promise對象,可以使用then方法增加回調函數。

async function helloAsync(){
    return "helloAsync";
  }
  
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
 
helloAsync().then(v=>{
   console.log(v);         // helloAsync
})

async關鍵字修飾的函數中,可以使用await關鍵字。await關鍵字只能在async函數中使用,用於讓一個Promise對象等待,在async函數外使用會報錯。被await關鍵字修飾的Promise對象,await將等待Promise正常處理完成後返回其處理結果。

function testAwait (x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}
 
async function helloAsync() {
  var x = await testAwait ("hello world");
  console.log(x); 
}
helloAsync ();
// hello world

正常情況下,await後面是Promise對象,但是也可以跟其他對象

function testAwait(){
   console.log("testAwait");
}
async function helloAsync(){
   await testAwait();
   console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync

對於Promise對象,await會暫停執行,等待Promise對象resolve,然後回覆async函數的執行並返回解析值。非Promise對象會直接返回對應的值。

參考資料

[1] 菜鳥教程ES6


我和幾位大佬建立了一個微信公衆號,歡迎關注後查看更多技術乾貨文章
歡迎加入交流羣QQ1107710098
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章