ES6真是顛覆JavaScript的東西基公,隨便翻一個(gè)新特性出來(lái)幅慌,就讓自以為是的老古董們傻眼跳樓。在之前接觸ember.js的時(shí)候酌媒,接觸到了yield欠痴,嫩是半天沒(méi)明白,yield到底是什么秒咨,想要實(shí)現(xiàn)什么目的喇辽。后來(lái)在看ES6的東西的時(shí)候,總算好像知道了一點(diǎn)雨席,迫不及待的寫(xiě)出來(lái)菩咨。
在MDN上,對(duì)yield的第一句解釋就是:
The yield keyword is used to pause and resume a generator function.
// yield這個(gè)關(guān)鍵字是用來(lái)暫停和恢復(fù)一個(gè)遍歷器函數(shù)(的運(yùn)行)的陡厘。
這也就是yield的所有解釋了抽米,可謂大道至簡(jiǎn),然并卵糙置,深層的意思不去挖掘云茸,根本還是沒(méi)法用它,還是老老實(shí)實(shí)做老古董谤饭。
關(guān)鍵字yield
沒(méi)錯(cuò)标捺,yield是個(gè)關(guān)鍵字,不是函數(shù)揉抵。關(guān)鍵字用來(lái)干啥亡容?它的作用是“命令”。和var不同冤今,不是用來(lái)聲明闺兢,但是和return一樣,用來(lái)告知程序某種狀態(tài)戏罢,return告訴程序要返回什么值(也意味著結(jié)束屋谭,結(jié)束的時(shí)候才會(huì)返回值嘛),而yield告訴程序當(dāng)前的狀態(tài)值龟糕,而且你運(yùn)行到這里給我暫停一下戴而。
因?yàn)閥ield是命令型的關(guān)鍵字,所以它的用法是:
[rv] = yield [expression];
rv是可選的翩蘸,這里不是說(shuō)它返回一個(gè)數(shù)組所意。yield后面的表達(dá)式也是可選的。yield的返回值是一個(gè)狀態(tài)值催首。如果從返回值的角度講扶踊,yield還可以當(dāng)做是一種運(yùn)算符,但是由于它的作用是暫停和恢復(fù)郎任,所以嚴(yán)格意義上說(shuō)秧耗,不能叫運(yùn)算符,運(yùn)算符是用來(lái)運(yùn)算的舶治,而yield是用來(lái)“命令”的分井。
遍歷器函數(shù)(Generator)
這是ES6里面新增的Generator车猬,為了把它和我們已知的東西聯(lián)系起來(lái),我把它翻譯為遍歷器函數(shù)尺锚,但是實(shí)際上現(xiàn)在還沒(méi)有統(tǒng)一的叫法珠闰,坐等大神們開(kāi)宗立派。
我個(gè)人的理解瘫辩,Generator函數(shù)的最大用處就是用來(lái)生成一個(gè)遍歷器伏嗜。
不過(guò)它是一個(gè)函數(shù),所以和普通的函數(shù)有點(diǎn)區(qū)別伐厌,因此在聲明函數(shù)的時(shí)候承绸,要在function和函數(shù)名之間加一個(gè)*號(hào):
function *foo() {}
而yield也必須在Generator函數(shù)中才有意義,脫離了Generator就沒(méi)意義了啊挣轨,這就像說(shuō)“鯊魚(yú)是用來(lái)在海里面消滅傻瓜魚(yú)的”現(xiàn)在你吧鯊魚(yú)丟到陸地上军熏,請(qǐng)問(wèn)它有啥意義?給人做包包嗎卷扮?
Generator函數(shù)的一個(gè)重要特點(diǎn)就是需要執(zhí)行next()方法才能運(yùn)行羞迷,聲明好它之后,根本不會(huì)馬上運(yùn)行画饥。舉個(gè)栗子:
var a = 0;
function *foo() {
a += 1;
yield '';
return;
}
var f = foo();
alert(a); // 這個(gè)時(shí)候是啥值衔瓮?
上面的例子如此簡(jiǎn)單,你不會(huì)看不懂吧抖甘?現(xiàn)在告訴你热鞍,alert的結(jié)果是0!
我前面都說(shuō)了衔彻,要執(zhí)行netx()方法才會(huì)運(yùn)行薇宠。所以在上面的代碼末尾添加:
f.next();
alert(a); // 這個(gè)時(shí)候就會(huì)alert(1)了
沒(méi)怎明白?繼續(xù)往下讀
.next()方法
就用上面的代碼來(lái)說(shuō)好了艰额。Generator函數(shù)的特點(diǎn)就是“它是一個(gè)遍歷器”澄港,你不讓它動(dòng),它絕對(duì)不會(huì)動(dòng)柄沮。
不要用“動(dòng)”這么猥瑣的詞好嗎回梧?
“執(zhí)行、運(yùn)行”祖搓,當(dāng)調(diào)用f.next()的時(shí)候狱意,程序從f這個(gè)遍歷器函數(shù)的開(kāi)始運(yùn)行,當(dāng)遇到y(tǒng)ield命令時(shí)拯欧,表示“暫拖甓冢”,并且返回yield [expression]的狀態(tài)镐作。比如程序在往下運(yùn)行的時(shí)候藏姐,遇到y(tǒng)ield 1 + 2隆箩,那么next()返回的結(jié)果就是這個(gè)時(shí)候的狀態(tài)。
而f.next()就是讓它往下一個(gè)元素遍歷的動(dòng)作羔杨,它的返回值其實(shí)表示一個(gè)狀態(tài)捌臊,是一個(gè)object:{value: xxx, done: false}。value表示yield后面的表達(dá)式的結(jié)果值问畅,done表示是否已經(jīng)遍歷完娃属。把上面的f.next()那段代碼改成下面的:
var s1 = f.next();
console.log(s1); // {value: '',done: false}
var s2 = f.next();
console.log(s2); // {value: undefined,done: true}
第一個(gè)console的地方六荒,因?yàn)榈谝淮螆?zhí)行f.next()护姆,遇到y(tǒng)ield就暫停了,得到s1這個(gè)狀態(tài)掏击,這個(gè)狀態(tài)的value是yield后面緊跟的表達(dá)式的值卵皂,done表示遍歷沒(méi)有結(jié)束,還可以繼續(xù)執(zhí)行next()方法砚亭。第二次再執(zhí)行f.next()的時(shí)候灯变,遇到了return,但后面沒(méi)有表達(dá)式捅膘,所以返回值是undefined添祸,一旦遇到return就表示遍歷可以結(jié)束了,所以done為true寻仗。
當(dāng)運(yùn)行到yield '';
的時(shí)候刃泌,程序暫停了,不會(huì)往下繼續(xù)執(zhí)行署尤,所以下面的各種加減乘除都不會(huì)運(yùn)行耙替,這也就是為什么我們上面的代碼在運(yùn)行f.next()之前,雖然執(zhí)行了foo()這個(gè)函數(shù)曹体,但是a的值是0俗扇,就是因?yàn)檫€沒(méi)有執(zhí)行f.next(),所以yield前面的a += 1還沒(méi)有運(yùn)行箕别。
也正是因?yàn)橛?next()方法铜幽,所以它叫遍歷器。for...of串稀,...啥酱,Array.from都是利用遍歷器接口進(jìn)行運(yùn)算的,所以如果沒(méi)有Generator厨诸,這幾個(gè)方法就用不了镶殷。而在Babel里面,babel-core本身默認(rèn)不支持Generator微酬,所以這幾個(gè)運(yùn)算其實(shí)也需要另外的模塊( babel-polyfill )來(lái)支持绘趋。
一不小心講到了babel颤陶,罪過(guò)罪過(guò)。
.next()的參數(shù)
造孽的地方終于要來(lái)了陷遮。一言不合滓走,就上代碼:
var foo = function *() { // 沒(méi)錯(cuò),尼瑪還可以這樣寫(xiě)
var x = 1;
var y = yield (x + 1);
var z = yield (x + y);
return z;
}() // 你必須先執(zhí)行一下Generator函數(shù)帽馋,才能把遍歷器返回給某個(gè)變量
var a = foo.next(); // 第一次執(zhí)行next()不可以傳參
var b = foo.next(3);
var c = foo.next(4);
求abc的value各是多少搅方?
你要沒(méi)接觸過(guò),這個(gè)時(shí)候只會(huì)冒出來(lái)“吊雷老某”……a.value你應(yīng)該知道绽族,就2(x = 1, x + 1 = 2)姨涡。b.value呢?傻戳戳了吧吧慢。
.next()的參數(shù)的意思是將傳入的參數(shù)用作上一次的yield涛漂。啥子意思?就是第二次執(zhí)行foo.next(3)的時(shí)候检诗,yield x + 1這一大坨就是3匈仗,所以y = 3!“xx老謀”逢慌。所以悠轩,b.value的結(jié)果就是4 (x = 1,y = 3, x + y = 4).
這樣推下去咯。最后返回的是z攻泼,但是傳入的是4火架,yield (x + y) 這一大坨就用4來(lái)代替,z.value = yield (x + y) = 4坠韩。
兩個(gè)問(wèn)題:1. 為什么第一次執(zhí)行next()不能傳參距潘?2.為什么yield后面要加括號(hào)?
第一個(gè)問(wèn)題只搁,因?yàn)榈谝淮螆?zhí)行next()的時(shí)候音比,你傳入的參數(shù)沒(méi)法去代替某一個(gè)yield啊氢惋!
第二個(gè)問(wèn)題洞翩,其實(shí)可以不用加括號(hào),但是如果不加括號(hào)的話焰望,那么后面的+運(yùn)算就沒(méi)了骚亿,比如說(shuō)上面的代碼改為:
var foo = function *() {
var x = 1;
var y = yield (x + 1);
var z = yield x + y;
return z;
}()
var a = foo.next();
var b = foo.next(3);
var c = foo.next(4);
這差距就大了,yield x是一坨熊赖,不會(huì)等到你x+y它就會(huì)返回来屠,所以b還是1.
把yield當(dāng)變量看
上面這一個(gè)小節(jié)你有沒(méi)有發(fā)現(xiàn)一個(gè)神奇的規(guī)律?就是你在給next()傳參的時(shí)候,總是對(duì)應(yīng)某一個(gè)yield俱笛,把這個(gè)yield以及后面緊跟著的表達(dá)式用傳入的參數(shù)代替捆姜。于是乎,下面這個(gè)代碼你就懂了:
var foo = function *() {
var x = 1;
var y = yield (x + 1);
var z = yield;
return z;
}()
var a = foo.next();
var b = foo.next(3);
var c = foo.next(4);
上面這個(gè)代碼里面迎膜,竟然把yield后面的表達(dá)式給去掉了泥技。那會(huì)有啥影響?b.value將等于undefined磕仅,僅此而已珊豹。因?yàn)閎這個(gè)位置就是得到y(tǒng)ield [空]的結(jié)果,所以就是undefined咯榕订。但是當(dāng)foo.next(4)執(zhí)行的時(shí)候店茶,不管你上面是不是undefined,我現(xiàn)在就是要用4來(lái)覆蓋你卸亮,所以z.value還是4.
也正因?yàn)檫@樣忽妒,在字符串里面玩裙,可以這樣使用:
var log = function *() {
console.log(`you input: ${yeild}`)
}().next(); // 這里會(huì)提示錯(cuò)誤: yeild undefined
log.next('hello world!');
上面這個(gè)例子把yield當(dāng)做一個(gè)變量來(lái)使用兼贸,如果你感興趣,可以想辦法吧第一次的時(shí)候錯(cuò)誤去除掉吃溅。
這個(gè)用法看上去很挫溶诞,但是很厲害的是,可以通過(guò)這個(gè)思路决侈,實(shí)現(xiàn)字符串模板的次第渲染螺垢。可以控制渲染到哪個(gè)位置赖歌。
總結(jié)一下
寫(xiě)了這么多枉圃,總結(jié)一下yield,實(shí)際上:
- 只能在Generator函數(shù)內(nèi)部使用
- 運(yùn)行.next()庐冯,遇到一個(gè)yield命令孽亲,就暫停
- .next()的返回值表示一個(gè)狀態(tài){value,done}
- 再運(yùn)行.next(),從之前遇到的那個(gè)yield [表達(dá)式]處(后)繼續(xù)恢復(fù)運(yùn)行
- 當(dāng).next()傳參的時(shí)候展父,yield [表達(dá)式]整個(gè)被替換為傳入的參數(shù)返劲。
最后,說(shuō)一下for...of是怎么運(yùn)行的栖茉。
function *foo() {
yield 1;
yield 2;
yield 3;
return;
}
for(let v of foo()) {
console.log(v);
}
for...of在遍歷foo()返回的結(jié)果時(shí)篮绿,每遇到一個(gè)yield,就把yield后面表達(dá)式的結(jié)果作為of前面的v的值進(jìn)行賦值(next()返回值的value字段)吕漂。沒(méi)錯(cuò)亲配,就這么不要臉的解釋完了。