簡述了iterator,genteator和async的關係,和他們的基本原理以及用例。 ...
1.遍歷器iterator
1.1 for遍歷
首先從遠古講起,剛出js的時候如何遍歷一個數組呢?
var arr = [1, 2, 3, 4, 7, 8, 9]
for (let i = 0;i < arr.length;i++) {
console.log(arr[i]);
}
1.2 forEach遍歷
看起來笨的一批,所以ES5給你研究了一個foreach方法,但是這個方法不能break,也不能改變數組自身的值,也不能返回任何值。
var arr = [1, 2, 3, 4, 7, 8, 9]
var arr2 = arr.forEach((element, index) => {
console.log('第' + index + '個數值為:' + element);
return element * 2;
});
console.log(arr2) // undefined
console.log(arr1) // [1, 2, 3, 4, 7, 8, 9]
所以說foreach只給你最基本的操作,其他一概不管,如果你想要改變自身的值或者有break和countinue操作我們可以使用map操作,不展開講了,之前專門寫了篇博客總結了下。
wzr:數組遍歷方法總結
1.3 for-in遍歷
那麼ES6專門為遍曆數組提供了一種方法,就是for-of。說道for-of,不得不提到了for-in
那麼關於他們倆的區別,我也專門寫了一篇博客,不在這展開講了。
值得一提的是for-in是可以遍曆數組的,但是不推薦用for-in去遍曆數組,為什麼呢?因為for -in 返回的可枚舉屬性是字元類型,不是數字類型,如果"0","1"這樣的屬性和1,2數組發生相加,很可能不是直接相加,而是字元串的疊加。例如
const items = [1,2,3,4]
for(item in items){
let tempitem = item+1
console.log(items[tempitem]) // undefined
console.log(tempitem) // 01 21 32 41 item與數字相加 會得到字元串相加的結果,所以導致上面找不著items相應的項
}
所以為了避免歧義,還是不要用for-in遍曆數組的好。
1.4 for-of
那麼接下來就進入正題了,因為for-in在數組這邊比較難用,所以ES6新添加的for-of來彌補for-in的不足。這是個正兒八經遍曆數組的方法。與 forEach() 不同的是,它支持 break、continue 和 return 語句。而且他本身的語法非常的簡單:
for (variable of iterable) {
//statements
}
variable: 在每次迭代中,將不同屬性的值分配給變數。
iterable: 被迭代枚舉其屬性的對象。
而且關鍵的問題是for-of不僅可以遍曆數組,他可以遍歷很多類數組對象。
- Array
- Map
- Set
- String
- TypedArray
- 函數的 arguments 對象
- NodeList 對象
而他的原理在於這些類數組對象中都有一個屬性,就是Symbol.iterator
,也就是說,只要帶Symbol.iterator
他都能遍歷,我們單獨把這個屬性拿出來,自己手動執行next()方法就會看到我們成功的遍歷了這個數組:
const items = [1,2,3,4]
const giao = items[Symbol.iterator]()
console.log(giao.next()) // {value: 1, done: false}
console.log(giao.next()) // {value: 2, done: false}
console.log(giao.next()) // {value: 3, done: false}
console.log(giao.next()) // {value: 4, done: false}
console.log(giao.next()) // {value: undefined, done: true}
同理,我們可以已通過手動寫一個iterator來更深入的瞭解他的原理:
Array.prototype.myiterator = function(){
let i = 0
let items = this
return {
next(){
const done = i >= items.length
const value = done ? undefined : items[i++]
return {
value, done
}
}
}
}
const item = [1,2,3,4]
// 控制台
> const giao = item.myiterator() //當我們獲得到遍歷器時,我們只需要代替for-of執行myiterator即可遍歷這個數組。
> giao.next()
{value: 1, done: false}
> giao.next()
{value: 2, done: false}
> giao.next()
{value: 3, done: false}
> giao.next()
{value: 4, done: false}
> giao.next()
{value: undefined, done: true}
效果跟for of是一樣的。另外值得註意的是,你可以在任意對象里添加這個屬性,讓他們可遍歷。
const items = [ 'blue', 'yellow', 'white', 'black']
for(item of items){
console.log(item)
}
1.5總結
遍歷器如果存在與一個對象內,他就可以讓這個對象可供for-of遍歷,for-of的遍歷方法就是不停地調用遍歷器的next()
方法,直到done
屬性變為true
。
2.生成器 Generator
為什麼拿生成器和遍歷器一起講呢,因為本質上,生成器器函數返回的就是一個遍歷器!
生成器的語法很簡單,就是在function後面加個*
,然後用yield來返回對應的值。(其實也可以將yield可以看做return,只不過需要next()來進行外調用,還有一個函數只能由一個return ,而yield可以有多個)
function* items(){
yield "1"
yield "2"
yield "3"
}
const num = items()
// 控制台
> num.next()
{value: "1", done: false}
> num.next()
{value: "2", done: false}
> num.next()
{value: "3", done: false}
> num.next()
{value: undefined, done: true}
> num.next()
{value: undefined, done: true}
那麼我們yield的之前同樣也可以加入運算
function* items(){
let i =0
yield i // 0
i++
yield i // 1
i++
yield i // 2
i++ //這個就不運行了,因為他在yield之後
}
const num = items()
//不用瀏覽器控制台,直接列印也行
console.log(num.next()) // {value:0,done:false}
console.log(num.next()) // {value:1,done:false}
console.log(num.next()) // {value:2,done:false}
console.log(num.next()) // {value:undefined,done:true}
利用這樣的特性,我們可以用Generator來進行ajax的等待操作。
function ajax(url){
// 請求成功自動調用next()方法。然後返回數據結果
axios.get(url).then(res => gen.next(res.data))
}
function* step(){
const class = yield ajax.get(`http://laotie.com/getclass`)
const score = yield ajax.get(`http://laotie.com/getscore?name=${class[0].name})
}
// 獲得這個函數的"遍歷器"
const gen = step()
// 啟動"遍歷器",不啟動就不會動
gen.next() // 獲取class
gen.next() // 獲取到score
因為第二個get請求依賴第一個請求的結果,所以我們解決辦法第一個是運用Promise的回調來限制他們的先後順序,但是在我們學習了生成器之後我們發現生成器的特性很適合做這樣的事。也就是只有當第一個請求執行完之後,才能順序執行第二個請求。
另外還有一些小的特性
*
可以添加到任意位置,都不會影響生成genterator。下麵的寫法都是可以的。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
關於Generator的Thunk或者co模塊,因為ES8的async的加入,極大地簡化了Genrtator的操作,針對原理和實戰操作也就沒有那麼多要求了,接下來主要說一說async函數。
3.async
1.語法
準確的說,async就是Generator的語法糖,首先看他的語法。
async function laotie(){
const data = await dosomething()
}
可以看到,
- 原來的
*
由async
代替 - 原來的
yield
由await
代替
這樣做的直接的好處就是顯得更加語義化,可讀性更強。但是其實async做到的遠不止如此。
首先第一點,就是async不用不斷執行next()了,async函數內置了執行器,使得我們在調用函數時,只需要直接調用即可。如下:
// 接上一個代碼塊 laotie()
現在async 的
await
還是保留著等待的功能,但是因為沒有了next()
,所以在調用await
不會像yield
那樣返回值了。在async中,只有return返回,而且返回的是一個promise對象。拿上面的代碼直接改寫成async加await的格式,
async function items(){ let i =0 await i i++ await i i++ await i i++ } console.log(items()) // Promise {<pending>}
直接調用方法我們能看到返回的是一個狀態為resolved的Promise對象,而不是Iterator。
而這個對象,返回的值就是函數里return出來的值。我們可以用then()函數來接受這個值並列印他。
async function items(){ let i =3 return i } items().then(res=>{ console.log(res) // 3 })
當然這麼舉例子准定不是正經的用法,這些例子主要用於區分Generator和async函數之前的區別。
2.用法
正確的用法現在是在await之後加入一個非同步函數,await相當於將這個非同步函數轉化為同步函數,等這個非同步函數執行完畢返回resolved的時候時候才往下執行進一步的操作。例如:
async function asyncPrint(value, ms) {
await new Promise(resolve => {
setTimeout(resolve, ms);
});
console.log(value);
}
asyncPrint('hello world', 1000); // 一秒後列印hellow world
如果這個async 的函數中間有多個await,那麼就讓多個await以排隊的方式執行。
用法2:
先讓我們把之前generator的例子拿過來
function ajax(url){
// 請求成功自動調用next()方法。然後返回數據結果
axios.get(url).then(res => gen.next(res.data))
}
function* step(){
const class = yield ajax.get(`http://laotie.com/getclass`)
const score = yield ajax.get(`http://laotie.com/getscore?name=${class[0].name})
}
// 獲得這個函數的遍歷器
const gen = step()
// 啟動遍歷器
gen.next()
寫著挺累的,但是async可以快速的簡化它。因為await接受的就是一個Prmoise函數,所以我們可以直接在await後面使用axios,然後直接使用對象解構來獲取相應的值。
async function step(){
const {data:{class}} = await axios.get(`http://laotie.com/getclass`)
const {data:{core}} = await axios.get(`http://laotie.com/getscore?name=${class[0].name})
return {class,core}
}
這樣是不是就方便多啦!