你們好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新......前端
- github:https://github.com/Daotin/Web
- 微信公衆號:Web前端之巔
- 博客園:http://www.cnblogs.com/lvonve/
- CSDN:https://blog.csdn.net/lvonve/
在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的項目。如今就讓咱們一塊兒進入 Web 前端學習的冒險之旅吧!git
如下來自 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
表達式(hello
和world
),即該函數有三個狀態: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
方法,就會返回一個有着value
和done
兩個屬性的對象。value
屬性表示當前的內部狀態的值,是yield
表達式後面那個表達式的值;done
屬性是一個布爾值,表示是否遍歷結束。
因爲 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
。
任意一個對象的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 接口,能夠被...
運算符遍歷了。
yield
表達式自己沒有返回值,或者說老是返回undefined
。next
方法能夠帶一個參數,該參數就會被看成上一個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
循環能夠自動遍歷 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
屬性爲true
,for...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();
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
命令後面是一個 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')
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
方法是類的默認方法,經過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
對象。