多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
``` function gen(){   while(true){     yield "gen\n";   } } $gen = gen(); var_dump($gen instanceof Iterator); echo "hello, world!"; ``` 如果事先没了解过yield,可能会觉得这段代码一定会进入死循环。但是我们将这段代码直接运行会发现,输出hello, world!,预想的死循环没出现。 究竟是什么样的力量,征服了while(true)呢,接下来就带大家一起来领略一下yield关键字的魅力。 首先要从foreach说起,我们都知道对象,数组和对象可以被foreach语法遍历,数字和字符串缺不行。其实除了数组和对象之外PHP内部还提供了一个 Iterator 接口,实现了Iterator接口的对象,也是可以被foreach语句遍历,当然跟普通对象的遍历就很不一样了。 以下面的代码为例: ``` class Number implements Iterator{   protected $key;   protected $val;   protected $count;   public function __construct(int $count){     $this->count = $count;   }   public function rewind(){     $this->key = 0;     $this->val = 0;   }   public function next(){   $this->key += 1;   $this->val += 2;   }   public function current(){     return $this->val;   }   public function key(){   return $this->key + 1;   }   public function valid(){     return $this->key < $this->count;   } } foreach (new Number(5) as $key => $value){   echo "{$key} - {$value}\n"; } ``` 这个例子将输出     1 - 0     2 - 2     3 - 4     4 - 6     5 - 8 关于上面的number对象,被遍历的过程。如果是初学者,可能会出现有点懵的情况。为了深入的了解Number对象被遍历的时候内部是怎么工作的,我将代码改了一下,将接口内的每个方法都尽心输出,借此来窥探一下遍历时对象内部方法的的执行情况。 ``` class Number implements Iterator{ protected $i = 1; protected $key; protected $val; protected $count; public function __construct(int $count){ $this->count = $count; echo "第{$this->i}步:对象初始化.\n"; $this->i++; } public function rewind(){ $this->key = 0; $this->val = 0; echo "第{$this->i}步:rewind()被调用.\n"; $this->i++; } public function next(){ $this->key += 1; $this->val += 2; echo "第{$this->i}步:next()被调用.\n"; $this->i++; } public function current(){ echo "第{$this->i}步:current()被调用.\n"; $this->i++; return $this->val; } public function key(){ echo "第{$this->i}步:key()被调用.\n"; $this->i++; return $this->key; } public function valid(){ echo "第{$this->i}步:valid()被调用.\n"; $this->i++; return $this->key < $this->count; } } $number = new Number(5); echo "start...\n"; foreach ($number as $key => $value){ echo "{$key} - {$value}\n"; } echo "...end...\n"; ``` 第1步:对象初始化. start... 第2步:rewind()被调用. 第3步:valid()被调用. 第4步:current()被调用. 第5步:key()被调用. 0 - 0 第6步:next()被调用. 第7步:valid()被调用. 第8步:current()被调用. 第9步:key()被调用. 1 - 2 第10步:next()被调用. 第11步:valid()被调用. 第12步:current()被调用. 第13步:key()被调用. 2 - 4 第14步:next()被调用. 第15步:valid()被调用. 第16步:current()被调用. 第17步:key()被调用. 3 - 6 第18步:next()被调用. 第19步:valid()被调用. 第20步:current()被调用. 第21步:key()被调用. 4 - 8 第22步:next()被调用. 第23步:valid()被调用. ...end... 那么这个跟yield有什么关系呢,这便是我们接下来要说的重点了。首先给大家介绍一下我总结出来的 yield 的特性,包含以下几点。 **1.yield只能用于函数内部,在非函数内部运用会抛出错误** **2.如果函数包含了yield关键字的,那么函数执行后的返回值永远都是一个Generator对象。** **3.如果函数内部同事包含yield和return 该函数的返回值依然是Generator对象,但是在生成Generator对象时,return语句后的代码被忽略。** **4.Generator类实现了Iterator接口** **5.可以通过返回的Generator对象内部的方法,获取到函数内部yield后面表达式的值** **6.可以通过Generator的send方法给yield 关键字赋一个值**。 **7.一旦返回的Generator对象被遍历完成,便不能调用他的rewind方法来重置** **8.Generator对象不能被clone关键字克隆** **9.如果想要return 可以调用Generator的getReturn()方法。** 首先看第1点,可以明白我们文章开头的gen函数执行后返回的是一个Generatory对象,所以代码可以继续执行下去输出hello, world!,因此$gen是一个Generator对象,由于其实现了Iterator,所以这个对象可以被foreach语句遍历。下面我们来看看对其进行遍历,会是什么样的效果。为了防止被死循环,我加多了一个break语句只进行十次循环,方便我们了解yield的一些特性。