首先讓我們看下PHP官方文檔中是怎麼介紹的吧:產生器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 介面的方式,效能開銷和複雜性大大降低。
看了這句話,我們可以獲得幾個關鍵詞: 對象迭代、 Iterator 介面、效能開銷 ,比較抽象,talk is cheap show me the code,下面我們從一個產生器最經典的例子開始吧。
PHP中的range() 函數在使用的時候會在記憶體中建立一個包含指定範圍單元的數組並返回,一般來說,這個並沒什麼不妥,但是當所傳的limit入參值很大的時候,那麼也就意味著將會在記憶體中建立的數組也會很大,這個就太恐怖了,這是要幹爆記憶體的節奏啊。此時我們可以通過產生器來實現一個更高效的range函數,(下面的代碼是把PHP官方文檔中精簡處理了一下):
function xrange($start, $limit, $step = 1){ //校正參數,此處省略 for ($i = $start; $i <= $limit; $i += $step) { //向外產出值 yield $i; }}//xrange此時返回的是一個產生器對象$gen = xrange(1, 9);//對產生器進行迭代foreach ($gen as $number) { echo "$number ";}
這裡在xrange和range函數的效果相同,均是產生了一個可迭代的變數,但是不同的是,range函數有點像ORM裡面常說的 預先載入 ,而xrange則是 懶載入 只是等到迭代到那個點才會產生對應的值,因此xrange並不需要分配大塊記憶體來存放變數,大大節約了記憶體,提升效率。
現在我們來總結下產生器和普通函數有哪些異同:
產生器中必須包含yield關鍵字(用來產生結果),而且可以是多次出現,普通函數中向外部返回結果只能使用return,且函數執行完畢;
一個產生器不可以通過return傳回值,這樣做會產生一個編譯錯誤PHP Fatal error: Generators cannot return values using "return"(注意:這個在PHP7下面不會出錯,但是會終止產生器繼續執行,即調valid()方法會返回false,然而在PHP5中return空是一個有效文法並且它將會終止產生器繼續執行)
產生器類(Generator)
Generator 對象是從產生器返回的,上面代碼中$gen就是一個產生器對象。注意,產生器對象和其他類的對象不同,它並不能通過new關鍵字建立,只能從產生器函數擷取。首先我們看下Generator類摘要來看看其組成:
Generator implements Iterator{ /** * 返回當前產生的值(yield後面運算式的值) */ public mixed current ( void ) /** * yield的鍵(yield 'key'=>'val';) */ public mixed key ( void ) /** * 從上一個yield之後繼續執行,直到下一個yield */ public void next ( void ) /** * 重設迭代器(對於產生器並沒什麼卵用) */ public void rewind ( void ) /** * 向產生器中傳入一個值,並從上一個yield之後繼續執行 */ public mixed send ( mixed $value ) /** * 向產生器中拋出一個異常,並從上一個yield之後繼續執行 */ public void throw ( Exception $exception ) /** * 檢查迭代器是否被關閉(false表示已關閉) */ public bool valid ( void ) /** * 序列化回調,但是產生器不能被序列化,因此會拋出一個異常 */ public void __wakeup ( void )}
從上面的類摘要可以看出,Generator類是實現Iterator介面的,因此它具有迭代器的的特性。另外它加入了send()、throw()和__wekeup()方法,相關方法說明已經寫了注釋,在此不再贅述。
寫了一堆,發現我的文筆不好,還是畫個圖感受一下吧(圖畫的也不美觀,大家湊合著看吧,2333)
yield關鍵詞
接下來讓我們看下yield關鍵詞,它最簡單的調用形式看起來像普通函數的return,不同之處在於普通return會傳回值並終止函數的執行,而yield會返回一個值給迴圈調用此產生器的代碼,並且只是暫停執行產生器函數。
這是一個典型的yield運算式:$data = yield $key => $value;,該運算式包括兩部分:
注意:PHP5需要加上括弧$data = (yiel
d $key => $value);,否則會產生一個編譯錯誤,PHP7就不用關心這個了
這麼說可能有點抽象,還是吧:
產生器委託(yield from)
PHP7新增了yield from關鍵詞,該文法開始允許從其他的generator,Traversable對象,或者數組通過yield from 產生數函數 來yield值。yield from的各種特性與yield一樣都是產生資料,只是後面跟隨的運算式不同。下面看個例子(摘自PHP官方文檔):
function count_to_ten(){ yield 1; yield 2; yield from [3, 4]; //產生數組 yield from new ArrayIterator([5, 6]); //產生可遍曆對象 yield from seven_eight(); //產生產生器對象 yield 9; yield 10;}function seven_eight(){ yield 7; yield from eight();}function eight(){ yield 8;}foreach (count_to_ten() as $num) { echo "$num ";}//輸出:1 2 3 4 5 6 7 8 9 10
yield from以方便我們編寫比較清晰產生器嵌套,這點可以類比於函數中的嵌套調用,當函數A中調用另一個函數B,此時會等B執行完成並返回,方才繼續執行。在沒有yield from的時候,實現產生器嵌套需要自己實現棧並進行壓棧和彈出操作以達到相同效果,那是多麼痛苦的操作。