# 代碼寫了那么多,你搞明白yield是個啥沒?

ES6真是顛覆JavaScript的東西,隨便翻一個新特性出來,就讓自以為是的老古董們傻眼跳樓。在之前接觸ember.js的時候,接觸到了yield,嫩是半天沒明白,yield到底是什么,想要實現(xiàn)什么目的。后來在看ES6的東西的時候,總算好像知道了一點(diǎn),迫不及待的寫出來。

在MDN上,對yield的第一句解釋就是:

The yield keyword is used to pause and resume a generator function.
// yield這個關(guān)鍵字是用來暫停和恢復(fù)一個遍歷器函數(shù)(的運(yùn)行)的。

這也就是yield的所有解釋了,可謂大道至簡,然并卵,深層的意思不去挖掘,根本還是沒法用它,還是老老實實做老古董。

關(guān)鍵字yield

沒錯,yield是個關(guān)鍵字,不是函數(shù)。關(guān)鍵字用來干啥?它的作用是“命令”。和var不同,不是用來聲明,但是和return一樣,用來告知程序某種狀態(tài),return告訴程序要返回什么值(也意味著結(jié)束,結(jié)束的時候才會返回值嘛),而yield告訴程序當(dāng)前的狀態(tài)值,而且你運(yùn)行到這里給我暫停一下。

因為yield是命令型的關(guān)鍵字,所以它的用法是:

[rv] = yield [expression];

rv是可選的,這里不是說它返回一個數(shù)組。yield后面的表達(dá)式也是可選的。yield的返回值是一個狀態(tài)值。如果從返回值的角度講,yield還可以當(dāng)做是一種運(yùn)算符,但是由于它的作用是暫停和恢復(fù),所以嚴(yán)格意義上說,不能叫運(yùn)算符,運(yùn)算符是用來運(yùn)算的,而yield是用來“命令”的。

遍歷器函數(shù)(Generator)

這是ES6里面新增的Generator,為了把它和我們已知的東西聯(lián)系起來,我把它翻譯為遍歷器函數(shù),但是實際上現(xiàn)在還沒有統(tǒng)一的叫法,坐等大神們開宗立派。

我個人的理解,Generator函數(shù)的最大用處就是用來生成一個遍歷器。

不過它是一個函數(shù),所以和普通的函數(shù)有點(diǎn)區(qū)別,因此在聲明函數(shù)的時候,要在function和函數(shù)名之間加一個*號:

function *foo() {}

而yield也必須在Generator函數(shù)中才有意義,脫離了Generator就沒意義了啊,這就像說“鯊魚是用來在海里面消滅傻瓜魚的”現(xiàn)在你吧鯊魚丟到陸地上,請問它有啥意義?給人做包包嗎?

Generator函數(shù)的一個重要特點(diǎn)就是需要執(zhí)行next()方法才能運(yùn)行,聲明好它之后,根本不會馬上運(yùn)行。舉個栗子:

var a = 0;
function *foo() {
  a += 1;
  yield '';
  return;
}
var f = foo();
alert(a); // 這個時候是啥值?

上面的例子如此簡單,你不會看不懂吧?現(xiàn)在告訴你,alert的結(jié)果是0!

我前面都說了,要執(zhí)行netx()方法才會運(yùn)行。所以在上面的代碼末尾添加:

f.next();
alert(a); // 這個時候就會alert(1)了

沒怎明白?繼續(xù)往下讀

.next()方法

就用上面的代碼來說好了。Generator函數(shù)的特點(diǎn)就是“它是一個遍歷器”,你不讓它動,它絕對不會動。

不要用“動”這么猥瑣的詞好嗎?

“執(zhí)行、運(yùn)行”,當(dāng)調(diào)用f.next()的時候,程序從f這個遍歷器函數(shù)的開始運(yùn)行,當(dāng)遇到y(tǒng)ield命令時,表示“暫?!?,并且返回yield [expression]的狀態(tài)。比如程序在往下運(yùn)行的時候,遇到y(tǒng)ield 1 + 2,那么next()返回的結(jié)果就是這個時候的狀態(tài)。

而f.next()就是讓它往下一個元素遍歷的動作,它的返回值其實表示一個狀態(tài),是一個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}

第一個console的地方,因為第一次執(zhí)行f.next(),遇到y(tǒng)ield就暫停了,得到s1這個狀態(tài),這個狀態(tài)的value是yield后面緊跟的表達(dá)式的值,done表示遍歷沒有結(jié)束,還可以繼續(xù)執(zhí)行next()方法。第二次再執(zhí)行f.next()的時候,遇到了return,但后面沒有表達(dá)式,所以返回值是undefined,一旦遇到return就表示遍歷可以結(jié)束了,所以done為true。

當(dāng)運(yùn)行到yield '';的時候,程序暫停了,不會往下繼續(xù)執(zhí)行,所以下面的各種加減乘除都不會運(yùn)行,這也就是為什么我們上面的代碼在運(yùn)行f.next()之前,雖然執(zhí)行了foo()這個函數(shù),但是a的值是0,就是因為還沒有執(zhí)行f.next(),所以yield前面的a += 1還沒有運(yùn)行。

也正是因為有.next()方法,所以它叫遍歷器。for...of,...,Array.from都是利用遍歷器接口進(jìn)行運(yùn)算的,所以如果沒有Generator,這幾個方法就用不了。而在Babel里面,babel-core本身默認(rèn)不支持Generator,所以這幾個運(yùn)算其實也需要另外的模塊( babel-polyfill )來支持。

一不小心講到了babel,罪過罪過。

.next()的參數(shù)

造孽的地方終于要來了。一言不合,就上代碼:

var foo = function *() { // 沒錯,尼瑪還可以這樣寫
  var x = 1;
  var y =  yield (x + 1);
  var z = yield (x + y);
  return z;
}() // 你必須先執(zhí)行一下Generator函數(shù),才能把遍歷器返回給某個變量
var a = foo.next(); // 第一次執(zhí)行next()不可以傳參
var b = foo.next(3);
var c = foo.next(4);

求abc的value各是多少?

你要沒接觸過,這個時候只會冒出來“吊雷老某”……a.value你應(yīng)該知道,就2(x = 1, x + 1 = 2)。b.value呢?傻戳戳了吧。

.next()的參數(shù)的意思是將傳入的參數(shù)用作上一次的yield。啥子意思?就是第二次執(zhí)行foo.next(3)的時候,yield x + 1這一大坨就是3,所以y = 3!“xx老謀”。所以,b.value的結(jié)果就是4 (x = 1,y = 3, x + y = 4).

這樣推下去咯。最后返回的是z,但是傳入的是4,yield (x + y) 這一大坨就用4來代替,z.value = yield (x + y) = 4。

兩個問題:1. 為什么第一次執(zhí)行next()不能傳參?2.為什么yield后面要加括號?

第一個問題,因為第一次執(zhí)行next()的時候,你傳入的參數(shù)沒法去代替某一個yield??!

第二個問題,其實可以不用加括號,但是如果不加括號的話,那么后面的+運(yùn)算就沒了,比如說上面的代碼改為:

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是一坨,不會等到你x+y它就會返回,所以b還是1.

把yield當(dāng)變量看

上面這一個小節(jié)你有沒有發(fā)現(xiàn)一個神奇的規(guī)律?就是你在給next()傳參的時候,總是對應(yīng)某一個yield,把這個yield以及后面緊跟著的表達(dá)式用傳入的參數(shù)代替。于是乎,下面這個代碼你就懂了:

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);

上面這個代碼里面,竟然把yield后面的表達(dá)式給去掉了。那會有啥影響?b.value將等于undefined,僅此而已。因為b這個位置就是得到y(tǒng)ield [空]的結(jié)果,所以就是undefined咯。但是當(dāng)foo.next(4)執(zhí)行的時候,不管你上面是不是undefined,我現(xiàn)在就是要用4來覆蓋你,所以z.value還是4.

也正因為這樣,在字符串里面,可以這樣使用:

var log = function *() {
  console.log(`you input: ${yeild}`)
}().next(); // 這里會提示錯誤: yeild undefined
log.next('hello world!');

上面這個例子把yield當(dāng)做一個變量來使用,如果你感興趣,可以想辦法吧第一次的時候錯誤去除掉。

這個用法看上去很挫,但是很厲害的是,可以通過這個思路,實現(xiàn)字符串模板的次第渲染??梢钥刂其秩镜侥膫€位置。

總結(jié)一下

寫了這么多,總結(jié)一下yield,實際上:

  • 只能在Generator函數(shù)內(nèi)部使用
  • 運(yùn)行.next(),遇到一個yield命令,就暫停
  • .next()的返回值表示一個狀態(tài){value,done}
  • 再運(yùn)行.next(),從之前遇到的那個yield [表達(dá)式]處(后)繼續(xù)恢復(fù)運(yùn)行
  • 當(dāng).next()傳參的時候,yield [表達(dá)式]整個被替換為傳入的參數(shù)。

最后,說一下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é)果時,每遇到一個yield,就把yield后面表達(dá)式的結(jié)果作為of前面的v的值進(jìn)行賦值(next()返回值的value字段)。沒錯,就這么不要臉的解釋完了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容