從零開始學 Web 之 ES6(五)ES6基礎語法三

你們好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新......前端

在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的項目。如今就讓咱們一塊兒進入 Web 前端學習的冒險之旅吧!git

1、Generator

如下來自 ECMAScript 6 入門 - 阮一峯es6

Generator 函數是 ES6 提供的一種異步編程解決方案。github

Generator 函數有多種理解角度。語法上,首先能夠把它理解成,Generator 函數是一個狀態機,封裝了多個內部狀態。ajax

執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。編程

形式上,Generator 函數是一個普通函數,可是有兩個特徵。微信

一是,function關鍵字與函數名之間有一個星號;異步

二是,函數體內部使用yield表達式,定義不一樣的內部狀態(yield在英語裏的意思就是「產出」)。async

function* myGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
// 返回值是一個遍歷器對象
var hw = myGenerator();

上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(helloworld),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。異步編程

而後,Generator 函數的調用方法與普通函數同樣,也是在函數名後面加上一對圓括號。不一樣的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是上一章介紹的遍歷器對象(Iterator Object)。

下一步,必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

總結一下,調用 Generator 函數,返回一個遍歷器對象,表明 Generator 函數的內部指針。之後,每次調用遍歷器對象的next方法,就會返回一個有着valuedone兩個屬性的對象。value屬性表示當前的內部狀態的值,是yield表達式後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。

一、yield 表達式

因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield表達式就是暫停標誌。

遍歷器對象的next方法的運行邏輯以下:

(1)遇到yield表達式,就暫停執行後面的操做,並將緊跟在yield後面的那個表達式的值,做爲返回的對象的value屬性值。

(2)下一次調用next方法時,再繼續往下執行,直到遇到下一個yield表達式。

(3)若是沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,做爲返回的對象的value屬性值。

(4)若是該函數沒有return語句,則返回的對象的value屬性值爲undefined

須要注意的是,yield表達式後面的表達式,只有當調用next方法、內部指針指向該語句時纔會執行,所以等於爲 JavaScript 提供了手動的「惰性求值」(Lazy Evaluation)的語法功能。

yield表達式與return語句區別:

類似之處在於,都能返回緊跟在語句後面的那個表達式的值。區別在於每次遇到yield,函數暫停執行,下一次再從該位置繼續向後執行,而return語句不具有位置記憶的功能。

一個函數裏面,只能執行一次(或者說一個)return語句,可是能夠執行屢次(或者說多個)yield表達式。

正常函數只能返回一個值,由於只能執行一次return;Generator 函數能夠返回一系列的值,由於能夠有任意多個yield

二、與 Iterator 接口的關係

任意一個對象的Symbol.iterator方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。

因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具備 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

上面代碼中,Generator 函數賦值給Symbol.iterator屬性,從而使得myIterable對象具備了 Iterator 接口,能夠被...運算符遍歷了。

三、next 方法的參數

yield表達式自己沒有返回值,或者說老是返回undefinednext方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

上面代碼先定義了一個能夠無限運行的 Generator 函數f,若是next方法沒有參數,每次運行到yield表達式,變量reset的值老是undefined。當next方法帶一個參數true時,變量reset就被重置爲這個參數(即true),所以i會等於-1,下一輪循環就會從-1開始遞增。

四、for...of 循環

for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,且此時再也不須要調用next方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

上面代碼使用for...of循環,依次顯示 5 個yield表達式的值。這裏須要注意,一旦next方法的返回對象的done屬性爲truefor...of循環就會停止,且不包含該返回對象,因此上面代碼的return語句返回的6,不包括在for...of循環之中。

下面是一個利用 Generator 函數和for...of循環,實現斐波那契數列的例子。

function* foo() {
      let [prev, current] = [0, 1];
      for (;;) {
        yield current;
        [prev, current] = [current, prev + current];
      }
    }

    for (let n of foo()) {
      if (n > 1000) break;
      console.log(n);
    }

Generator小案例

需求:

一、發送Ajax請求獲取新聞內容

二、新聞內容獲取成功再次發送請求獲取對應的新聞評論內容

三、新聞內容獲取失敗則不須要再次發送請求。

function getNews(url) {
      $.get(url, function (data) {
        console.log(data);
        let urls = "http://localhost:3000" + data.commentUrl;
        // urls能夠做爲第一個yield的返回值
        // 執行第二條yeild語句,發送請求新聞評論
        // 獲取的評論地址如何傳入到 yield getNews(urls);靠的是第二次
        // 發送next時傳入的參數,就是評論地址
        sx.next(urls);
      });
    }

    function* sendXml() {
      // 發送請求新聞內容
      let urls = yield getNews("http://localhost:3000/news?id=2");
      // 請求新聞評論內容
      yield getNews(urls);
    }

    let sx = sendXml();
    // 執行第一條yeild語句,發送請求新聞
    sx.next();

2、async

ES2017 標準引入了 async 函數,使得異步操做變得更加方便。

async 函數是什麼?一句話,它就是 Generator 函數的語法糖。

語法:

async function foo(){
  await 異步操做;
  await 異步操做;
}

特色:

一、不須要像Generator去調用next方法,遇到await等待,當前的異步操做完成就往下執行

二、返回的老是Promise對象,能夠用then方法進行下一步操做

三、async取代Generator函數的星號*,await取代Generator的yield

四、語意上更爲明確,使用簡單,經臨牀驗證,暫時沒有任何反作用

舉個栗子:

async function timeout(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      })
    }
    
    async function asyncPrint(value, ms) {
      console.log('函數執行', new Date().toTimeString());
      await timeout(ms);
      console.log('延時時間', new Date().toTimeString());
      console.log(value);
    }

    console.log(asyncPrint('hello async', 2000));

asyncPrint 執行的時候,先打印的是「函數執行」,以後進入到 timeout 函數,因爲是異步執行,可是timeout未執行完成,因此 await 在等待,至關於掛起。而這一邊 asyncPrint會當即返回一個 Promise對象。以後另外一邊timeout、執行完成,打印出「延時時間」,以後打印「hello async」。

async函數內部return語句返回的值,會成爲then方法回調函數的參數。下面代碼中,函數f內部return命令返回的值,會被then方法回調函數接收到。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

一、await 命令

正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。

resolve參數就是await的返回值。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。

async function f() {
  await Promise.reject('出錯了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出錯了

二、案例:獲取新聞和評論內容

async function sendXml(url) {
      return new Promise((resolve, reject) => {
        $.ajax({
          url,
          type: 'GET',
          success: data => resolve(data),
          error: error => reject(error)
        })
      })
    }

    async function getNews(url) {
      let result = await sendXml(url);
      let result2 = await sendXml(url);
      console.log(result, result2);
    }
    getNews('http://localhost:3000/news?id=2')

3、Class

JavaScript 語言中,生成實例對象的傳統方法是經過構造函數。下面是一個例子。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面這種寫法跟傳統的面嚮對象語言(好比 C++ 和 Java)差別很大,ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。

//定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

一、constructor 方法

constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。

Class 的繼承

Class 能夠經過extends關鍵字實現繼承,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。

class Point {
}
// ColorPoint 繼承 Point
class ColorPoint extends Point {
}

上面代碼定義了一個ColorPoint類,該類經過extends關鍵字,繼承了Point類的全部屬性和方法。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調用父類的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調用父類的toString()
  }
}

上面代碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。

子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類本身的this對象,必須先經過父類的構造函數完成塑造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。

相關文章
相關標籤/搜索