摘要:函數(shù)可以沒有返回值,此時它依然返回一個并且在調(diào)用方法時一次行執(zhí)行完函數(shù)內(nèi)全部代碼,返回。將一個可遍歷結(jié)構(gòu)解構(gòu),并逐一返回其中的數(shù)據(jù)。
Generator
Generator 函數(shù)是 es6 中的新的異步編程解決方案,本節(jié)僅討論 Generator 函數(shù)本身,異步編程放在后面的部分。
Generator 函數(shù)之前也提到過,描述內(nèi)部封裝的多個狀態(tài),類似一個狀態(tài)機(jī),當(dāng)然也是很好的 iterator 生成器。Generator 函數(shù)的基本形式如下:
function* gen(){ yield status1; yield status2; //... }
不難看出,Generator 函數(shù)在 function 關(guān)鍵字和函數(shù)名之間加了一個星號"*", 內(nèi)部用 yield 返回每一個狀態(tài)。
當(dāng)然還有其他格式的定義:
//函數(shù)表達(dá)式 var gen = function*(){ yield status1; //... }; //對象方法 var obj = { *gen(){ yield status1; //... } };
Generator 函數(shù)調(diào)用時,寫法和普通函數(shù)一樣。但函數(shù)并不執(zhí)行執(zhí)行時,返回內(nèi)部自帶 iterator,之后調(diào)用該 iterator 的 next() 方法, 函數(shù)會開始執(zhí)行,函數(shù)每次執(zhí)行遇到 yield 關(guān)鍵字返回對應(yīng)狀態(tài),并跳出函數(shù),當(dāng)下一次再次調(diào)用 next() 的時候,函數(shù)會繼續(xù)從上一次 yield 跳出的下一跳語句繼續(xù)執(zhí)行。當(dāng)然 Generator 函數(shù)也可以用 return 返回狀態(tài),不過此時,函數(shù)就真的運(yùn)行結(jié)束了,該遍歷器就不再工作了;如果函數(shù)內(nèi)部所以的 yield 都執(zhí)行完了,該遍歷器一樣不再工作了:
function* gen(){ yield "hello"; yield "world"; return "ending"; } var it = gen(); console.log(it.next()); //{value: "hello", done: false} console.log(it.next()); //{value: "world", done: false} console.log(it.next()); //{value: "ending", done: true} console.log(it.next()); //{value: undefined, done: true}
注意:
return 返回的值,對應(yīng)的 done 屬性是 true。說明 return語句結(jié)束了遍歷,iterator 不再繼續(xù)遍歷,即便后面還有代碼和 yield。
Generator 函數(shù)可以沒有 yield 返回值,此時它依然返回一個 iterator, 并且在 iterator 調(diào)用 next 方法時一次行執(zhí)行完函數(shù)內(nèi)全部代碼,返回{value: undefined, done: true}。 如果有 return 語句,該返回值對應(yīng)的 value 屬性值為 return 表達(dá)式的值。
普通函數(shù)使用 yield 語句會報(bào)錯
yield 可以用作函數(shù)參數(shù),表達(dá)式參數(shù):
function* gen(){ console.log("hello" + (yield)); //yield 用作表達(dá)式參數(shù)必須加() let input = yield; foo(yield "a", yield "b"); }
Generator 函數(shù)的默認(rèn)遍歷器[Symbol.iterator]是函數(shù)自己:
function* gen(){} var g = gen() g[Symbol.iterator]() === g; //truenext() 參數(shù)
yield 語句本身具有返回值,返回值是下一次調(diào)用 next 方法是傳入的值。next 方法接受一個參數(shù),默認(rèn) undefined:
function* f(){ for(let i = 0; true; i++){ var reset = yield i; if(reset) i = -1; } } var g = f(); console.log(g.next().value) //0 console.log(g.next().value) //1 console.log(g.next().value) //2 console.log(g.next(true).value) //0
上面 代碼第3行var reset = yield i等號右側(cè)是利用 yield 返回i, 由于賦值運(yùn)算時右結(jié)合的,返回 i 以后,函數(shù)暫停執(zhí)行,賦值工作沒有完成。之后再次調(diào)用 next 方法時,將這次傳入?yún)?shù)作為剛才這個 yield 的返回值賦給了 reset, 因此計(jì)數(shù)器被重置。
function* foo(x){ var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var g = foo(5); console.log(g.next()); //{value: 6, done: false} console.log(g.next(12)); //{value: 8, done: false} console.log(g.next(13)); //{value: 42, done: true}
第一次調(diào)用 next 函數(shù)不需要參數(shù),作為 Generator 啟動,如果帶了參數(shù)也會被忽略。當(dāng)然,如果一定想在第一次調(diào)用 next 時候就賦值,可以將 Generator 函數(shù)封裝一下:
//一種不完善的思路,通常不強(qiáng)求這樣做 function wrapper(gen){ return function(){ let genObj = gen(...arguments); genObj.next(); //提前先啟動一次,但如果此時帶有返回值,該值就丟了! return genObj; } } var gen = wrapper(function*(){ console.log(`first input: "${yield}"`); }); var it = gen(); it.next("Bye-Bye"); //first input: "Bye-Bye"for...of
我們注意到,之前在 iterator 中,迭代器最后返回{value: undefined, done: true},其中值為 undefined 和 done 為 true 是同時出現(xiàn)的,而遍歷結(jié)果不包含 done 為 true 時對應(yīng)的 value 值,所以 Generator 的 for...of 循環(huán)最好不要用 return 返回值,因?yàn)樵撝祵⒉粫槐闅v:
function* gen(){ for(var i = 0; i < 5; i++){ yield i; } return 5; } for(let v of gen()){ console.log(v); //依次輸出 0, 1, 2, 3, 4, 沒有 5 }
除了 for...of, Generator 還有很多簡單用法。下面利用 fibonacci 數(shù)列,演示幾種不同的 Generator 用法:
展開運(yùn)算符
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } console.log([...fib(10)]); //1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
解構(gòu)賦值
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var [a, b, c, d, e, f] = fib(); //a=1, b=1, c=2, d=3, e=5, f=8
構(gòu)造函數(shù)參數(shù)
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var set = new Set(fib(n)); console.log(set); //Set(9) [1, 2, 3, 5, 8, 13, 21, 34, 55]
Array.from方法
function* fib(n = Infinity){ var a = 1, b = 1; var n = 10; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var arr = Array.from(fib(10)); console.log(arr); //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
遍歷對象
function* entries(obj){ for(let key of Object.keys(obj)){ yield [key, obj[key]]; } } var obj = { red: "#ff0000", green: "#00ff00", blue: "#0000ff" }; for(let [key, value] of entries(obj)){ console.log(`${key}: ${value}`); //依次輸出 "red: #ff0000", "green: #00ff00", "blue: #0000ff" }throw() 方法和 return() 方法
Generator 返回的遍歷器對象具throw() 方法, 一般的遍歷器用不到這個方法。該方法接受一個參數(shù)作為拋出的錯誤,該錯誤可以在 Generator 內(nèi)部捕獲:
function* gen(){ while(1){ try{ yield "OK"; } catch(e) { if(e === "a") console.log(`內(nèi)部捕獲: ${e}`); //內(nèi)部捕獲: a else throw e; } } } var it = gen(); it.next(); //如果沒有這一行啟動生成器,結(jié)果僅輸出:外部捕獲: a try{ it.throw("a"); it.throw("b"); it.next(); //上一行錯誤為外部捕獲,try 中的代碼不在繼續(xù)執(zhí)行,故這一行不執(zhí)行 } catch(e) { console.log(`外部捕獲: ${e}`) //外部捕獲: b }
throw參數(shù)在傳遞過程中和 next 參數(shù)類似,需要先調(diào)用一次 next 方法啟動生成器,之后拋出的錯誤會在前一個 yield 的位置被捕獲:
function* gen(){ yield "OK"; //錯誤被拋到這里,不在內(nèi)部 try 語句內(nèi)無法捕獲 while(1){ try{ yield "OK"; } catch(e) { console.log(`內(nèi)部捕獲: ${e}`); } } } var it = gen(); it.next(); try{ it.throw("a"); } catch(e) { console.log(`外部捕獲: ${e}`) //外部捕獲: a }
注意: 不要混用 throw() 方法和 throw 語句,后者無法將錯誤拋到生成器內(nèi)部。其次,throw 會終止遍歷器,不能繼續(xù)工作,而 throw 不會終止遍歷器:
function* gen(){ yield console.log("hello"); yield console.log("world"); } //throw 語句 var it1 = gen(); it1.next(); //hello try{ throw new Error(); } catch(e) { it1.next() //world } //throw() 方法 var it2 = gen(); it2.next(); //hello try{ it2.throw(); } catch(e) { it2.next() //遍歷器被關(guān)閉無法執(zhí)行, 靜默失敗 }
如果在遍歷器內(nèi)部拋出錯誤,遍歷器中止,繼續(xù)調(diào)用 next() 方法將得到{value: undefined, done: true}:
function* gen(){ var x = yield "ok"; var y = yield x.toUpperCase(); var z = yield (x + y + z); } //throw 語句 var it = gen(); it.next(); //"ok" try{ it.next(); } catch(e) { console.log("Error Caught"); //Error Caught } finally { it.next(); //{value: undefined, done: true} }
return() 方法返回指定的值,并終止迭代器:
var it = (function* gen(){ yield 1; yield 2; yield 3; }()); console.log(it.next()); //{value: 1, done: false} console.log(it.next()); //{value: 2, done: false} console.log(it.return("end")); //{value: "end", done: true} console.log(it.next()); //{value: undefined, done: true}
如果不給 return() 方法提供參數(shù),默認(rèn)是 undefined
如果 Generator 中有 try...finally 語句,return 會在 finally 執(zhí)行完再執(zhí)行:
function* numbers(){ yield 1; try{ yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); console.log(g.next().value); //1 console.log(g.next().value); //2 console.log(g.return("end").value); //延遲到 finally 之后輸出 ----- console.log(g.next().value); //4 | console.log(g.next().value); //5 | //"end" <------------------- console.log(g.next().value); //undefinedyield* 語句
在一個 Generator 中調(diào)用另一個 Generator 函數(shù)默認(rèn)是沒有效果的:
function* gen(){ yield 3; yield 2; } function* fun(){ yield gen(); yield 1; } var it = fun(); console.log(it.next().value); //gen 函數(shù)返回的遍歷器 console.log(it.next().value); //1 console.log(it.next().value); //undefined
顯然第一次返回的結(jié)果不是我們想要的。需要使用 yield 解決這個問題。yield 將一個可遍歷結(jié)構(gòu)解構(gòu),并逐一返回其中的數(shù)據(jù)。
function* gen(){ yield 3; yield 2; } function* fun(){ yield* gen(); yield 1; } var it = fun(); console.log(it.next().value); //3 console.log(it.next().value); //2 console.log(it.next().value); //1
function* fun(){ yield* [4,3,2]; yield 1; } var it = fun(); console.log(it.next().value); //4 console.log(it.next().value); //3 console.log(it.next().value); //2 console.log(it.next().value); //1
被代理的 Generator 可以用return向代理它的 Generator 返回值:
function* gen(){ yield "Bye"; yield* "Hi" return 2; } function* fun(){ if((yield* gen()) === 2) yield* "ok"; else yield "ok"; } var it = fun(); console.log(it.next().value); //Bye console.log(it.next().value); //H console.log(it.next().value); //i console.log(it.next().value); //o console.log(it.next().value); //k console.log(it.next().value); //undefined
舉例:
數(shù)組扁平化
//方法1: var arr = [1,2,[2,[3,4],2],[3,4,[3,[6]]]]; function plat(arr){ var temp = []; for(let v of arr){ if(Array.isArray(v)){ plat(v); } else { temp.push(v); } } return temp; } console.log(plat(arr)); //[1, 2, 2, 3, 4, 2, 3, 4, 3, 6] //方法2: function* plat2(arr){ for(let v of arr){ if(Array.isArray(v)){ yield* plat2(v); } else { yield v; } } } var temp = []; for(let x of plat2(arr)){ temp.push(x); } console.log(temp); //[1, 2, 2, 3, 4, 2, 3, 4, 3, 6]
遍歷二叉樹
//節(jié)點(diǎn) function Node(value, left, right){ this.value = value; this.left = left; this.right = right; } //二叉樹 function Tree(arr){ if(arr.length === 1){ return new Node(arr[0], null, null); } else { return new Node(arr[1], Tree(arr[0]), Tree(arr[2])); } } var tree = Tree([[[1], 4, [5]], 2, [[[0], 6, [9]], 8, [7]]]); //前序遍歷 function* preorder(tree){ if(tree){ yield tree.value; yield* preorder(tree.left); yield* preorder(tree.right); } } //中序遍歷 function* inorder(tree){ if(tree){ yield* inorder(tree.left); yield tree.value; yield* inorder(tree.right); } } //后序遍歷 function* postorder(tree){ if(tree){ yield* postorder(tree.left); yield* postorder(tree.right); yield tree.value; } } var _pre = [], _in = [], _post = []; for(let v of preorder(tree)){ _pre.push(v); } for(let v of inorder(tree)){ _in.push(v); } for(let v of postorder(tree)){ _post.push(v); } console.log(_pre); //[2, 4, 1, 5, 8, 6, 0, 9, 7] console.log(_in); //[1, 4, 5, 2, 0, 6, 9, 8, 7] console.log(_post); //[1, 5, 4, 0, 9, 6, 7, 8, 2]
Generator 實(shí)現(xiàn)狀態(tài)機(jī):
//傳統(tǒng)實(shí)現(xiàn)方法 var clock1 = function(){ var ticking = false; return { next: function(){ ticking = !ticking; if(ticking){ return "Tick"; }else{ return "Tock"; } } } }; var ck1 = clock1(); console.log(ck1.next()); //Tick console.log(ck1.next()); //Tock console.log(ck1.next()); //Tick //Generator 方法 var clock2 = function*(){ while(1){ yield "Tick"; yield "Tock"; } }; var ck2 = clock2(); console.log(ck2.next().value); //Tick console.log(ck2.next().value); //Tock console.log(ck2.next().value); //TickGenerator 函數(shù)中的 this
在ES6中, 規(guī)定了所有 iterator 是 Generator 函數(shù)的實(shí)例:
function* gen(){} var it = gen(); it instanceof gen; //true console.log(gen.__proto__); //GeneratorFunction console.log(gen.__proto__.__proto__); //Function console.log(gen.constructor); //GeneratorFunction console.log(gen.__proto__.constructor); //GeneratorFunction gen.prototype.sayHello = function(){ console.log("hello"); } it.sayHello(); //"hello"
但是 Generator 函數(shù)中的 this 并不指向生成的 iterator:
function* gen(){ this.num = 11; console.log(this); } var it = gen(); console.log(it.num); //undefined it.next(); //Window var obj = { * fun(){ console.log(this); } } var o_it = obj.fun(); o_it.next(); //obj
由上面這個例子不難看出,Generator 函數(shù)中的 this 和普通函數(shù)是一樣的。不過,可不可以把 Generator 函數(shù)作為構(gòu)造函數(shù)呢?顯然是不行的:
function* gen(){ this.num = 11; } gen.prototype.say = function(){console.log("hello")} var a = new gen(); //TypeError: gen is not a constructorGenerator 函數(shù)推導(dǎo)
ES7 在數(shù)組推導(dǎo)的基礎(chǔ)上提出了 Generator 函數(shù)推導(dǎo),可惜這個功能目前還不能使用:
let gen = function*(){ for(let i = 0; i < 6; i++){ yield i; } }; let arr = [for(let n of gen()) n * n]; //相當(dāng)于: let arr = Array.from(gen()).map(n => n * n); console.log(arr); [0,1,4,9,16,25]
Generator 數(shù)組推導(dǎo),利用惰性求值優(yōu)化系統(tǒng)資源利用:
var bigArr = new Array(10000); for(let i = 0; i < 10000; i++){ bigArr.push(i); } //....其他代碼 //使用 bigArr 之前很久就分配了內(nèi)存 console.log(bigArr[100]); var gen = function*(){ for(let i = 0; i < 10000; i++){ yield i; } }; //....其他代碼 //使用 bigArr 時才分配內(nèi)存 var bigArr = [for(let n of gen()) n]; console.log(bigArr[100]);應(yīng)用舉例
優(yōu)化回調(diào)函數(shù)
//偽代碼 function* main(){ var result = yield request("http://url.com"); var res = JSON.parse(result); console.log(res.value); } function request(url){ var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ it.next(xhr.response); } } xhr.send(); } var it = main(); it.next();
另一個例子:
//偽代碼 //遇到多重回調(diào)函數(shù),傳統(tǒng)寫法: step1(function(value1){ step2(value1, function(value2){ step3(value2, function(value3){ step4(value3, function(value4){ //do something }); }); }); }); //利用 Generator 寫: function* gen(){ try{ var value1 = yield step1(); var value2 = yield step2(value1); var value3 = yield step3(value2); var value4 = yield step4(value3); } catch(e) { //Handle the error form step1 to step4 } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97444.html
摘要:異步編程程序執(zhí)行分為同步和異步,如果程序每執(zhí)行一步都需要等待上一步完成才能開始,此所謂同步。因此異步編程十分重要。 異步編程 程序執(zhí)行分為同步和異步,如果程序每執(zhí)行一步都需要等待上一步完成才能開始,此所謂同步。如果程序在執(zhí)行一段代碼的同時可以去執(zhí)行另一段代碼,等到這段代碼執(zhí)行完畢再吧結(jié)果交給另一段代碼,此所謂異步。比如我們需要請求一個網(wǎng)絡(luò)資源,由于網(wǎng)速比較慢,同步編程就意味著用戶必須等...
摘要:由于中引入了許多數(shù)據(jù)結(jié)構(gòu)算上原有的包括等等數(shù)組需要一個東西來管理他們這就是遍歷器。數(shù)組默認(rèn)遍歷器遍歷值相當(dāng)于依次輸出依次輸出依次輸出依次輸出不難看出默認(rèn)得到值而只能得到索引。即遍歷器的本質(zhì)就是一個指針。 由于 ES6 中引入了許多數(shù)據(jù)結(jié)構(gòu), 算上原有的包括Object, Array, TypedArray, DataView, buffer, Map, WeakMap, Set, We...
摘要:允許我們把水平的代碼回調(diào)函數(shù)的地獄轉(zhuǎn)換為豎直的代碼在之前,我們使用或是,現(xiàn)在我們有了這里我們有個,執(zhí)行成功時調(diào)用的函數(shù)和失敗時調(diào)用的函數(shù)。使用的好處使用嵌套的回調(diào)函數(shù)處理錯誤會很混亂。 es6-參考手冊 該手冊包括ES2015[ES6]的知識點(diǎn)、技巧、建議和每天工作用的代碼段例子。歡迎補(bǔ)充和建議。 var 和 let / const 除了var,我們現(xiàn)在有了兩種新的標(biāo)示符用來存儲值——...
摘要:解構(gòu)賦值解構(gòu)賦值簡單來說就是對應(yīng)位置數(shù)組或?qū)?yīng)鍵名對象的變量匹配過程。字符串集合使用結(jié)構(gòu)賦值實(shí)現(xiàn)疊加并交換變量對象的解構(gòu)賦值對象的解構(gòu)賦值與變量位置次序無關(guān)只取決于鍵名是否嚴(yán)格相等。 解構(gòu)賦值 解構(gòu)賦值簡單來說就是 對應(yīng)位置(數(shù)組)或?qū)?yīng)鍵名(對象)的變量匹配過程。如果匹配失敗, 對于一般變量匹配不到結(jié)果就是 undefined, 對于具有展開運(yùn)算符(...)的變量結(jié)果就是空數(shù)組。 數(shù)...
摘要:基本類型是一種解決命名沖突的工具。這樣,就有了個基本類型和個復(fù)雜類型使用需要注意以下幾點(diǎn)和一樣不具有構(gòu)造函數(shù),不能用調(diào)用。判斷對象是否某個構(gòu)造函數(shù)的實(shí)例,運(yùn)算符會調(diào)用它是一個數(shù)組對象屬性。即,當(dāng)存在時,以此為構(gòu)造函數(shù)構(gòu)建對象。 Symbol基本類型 Symbol 是一種解決命名沖突的工具。試想我們以前定義一個對象方法的時候總是要檢查是否已存在同名變量: if(String && Str...
閱讀 1391·2023-04-26 03:04
閱讀 2329·2019-08-30 15:44
閱讀 3727·2019-08-30 14:15
閱讀 3507·2019-08-27 10:56
閱讀 2704·2019-08-26 13:53
閱讀 2616·2019-08-26 13:26
閱讀 3075·2019-08-26 12:11
閱讀 3609·2019-08-23 18:21