PHP 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关键字克隆
function gen(){
  while(true){
    yield "gen\n";
  }
}

$gen = gen();

var_dump($gen instanceof Iterator);
echo "hello, world!";
首先看第1点,可以明白gen函数执行后返回的是一个Generatory对象,
所以代码可以继续执行下去输出hello
, world!,因此$gen是一个Generator对象,由于其实现了Iterator,
所以这个对象可以被foreach语句遍历。下面我们来看看对其进行遍历,会是什么样的效果。为了防止被死循环,我加多了一个break语句只进行十次循环,方便我们了解yield的一些特性。 代码如下
:
    $i = 0;
    foreach ($gen as $key => $value) {
        echo "{$key} - {$value}";
        if(++$i >= 10){
            break;
        }
    }
以上代码输出:
    0 - gen
    1 - gen
    2 - gen
    3 - gen
    4 - gen
    5 - gen
    6 - gen
    7 - gen
    8 - gen
    9 - gen
通过观察不难发现其中的规律。在包含yield的函数返回的对象被foreach遍历时, 函数体内部的代码会被对应的执行。PHP会分析其内部的代码从而生成对应的Iterator接口的方法。
其中key方法实现是返回的是yield出现的次序,从0开始递增。
current方法则是yield后面表达式的值。
而valid方法则在当前yield语句存在的时候返回true, 如果当前不在yield语句的时候返回false。
next方法则执行从当前到下一个yield、或者return、或者函数结束之间的代码。
网上也有文章让大家把yield理解为暂时停止函数的执行,等待外部的激活从而再次执行。虽然看起来确实像那么回事,但我不建议大家这么理解,因为他本身是返回一个迭代器对象,
其返回值是可以被用于迭代的。我们理解了他被foreach迭代时,其内部是如运作的之后更易于理解yield关键字的本质。 下面我们再做一个简单的测试,以便更直观的展示他的特性。
    function gen1(){
        yield 1;
        echo "i\n";
        yield 2;
        yield 3+1;
    }
    $gen = gen1();
    foreach ($gen as $key => $value) {
        echo "{$key} - {$value}\n";
    }
以上代码输出:
    0 - 1
    i
    1 - 2
    2 - 4
我们来分析一下输出的结果,首先当遍历开始时rewind被执行由于第一个yield之前无任何语句,无任何输出。
key的值为yield出现的次序为0,current为yield表达式后的值也就是1。
foreach开始,valid因为当前为第一个yield,所以返回true。正常输出0 - 1
此时next方法被执行,跳转到了第二个yield,第一个到第二个之间的代码被执行输出了i。
再次进入循环 执行vaild,由于当前在第二个yield上面,所以依然是true
由于next执行了,所以key的值也有刚刚的0变为了1,current的值为2,正常输出 1 - 2。
这时候继续执行next(),进入循环vaild()执行,由于此时到了第三个yield返回依然是true。key的值为2, yield为4。正常输出 2 - 4 
再次执行next(),由于后续没有yield了vaild()返回为false, 所以循环到此便终止了。

下面我们用代码来验证一下:
    $gen = gen1();
    var_dump($gen->valid());
    echo $gen->key().' - '.$gen->current()."\n";
    $gen->next(); 
    var_dump($gen->valid());
    echo $gen->key().' - '.$gen->current()."\n";
    $gen->next(); 
    var_dump($gen->valid());
    echo $gen->key().' - '.$gen->current()."\n";
    $gen->next(); 
    var_dump($gen->valid());
 
 

输出值如下
    bool(true)
    0 - 1
    i
    bool(true)
    1 - 2
    bool(true)
    2 - 4
    bool(false)

跟我们的分析完全一致,至此我们了解了Iterator接口在遍历时内部的运作方式,也了解了包含yield关键字的函数所生成的对象内部是如何实现Iterator接口的方法的。对于yild的特性了解一半了,但是如果我们仅仅将其用于生成可以被遍历的对象的话,yield目前对我们来说,似乎无太大的用处。当然我们可以利用他来生成一些集合对象,节约一些内存知道数据真正被用到的时候在生成。例如:
我们可以写一个方法

    function gen2(){
        yield getUserData();
        yield getBannerList();
        yield getContext();
    }
    #中间其他操作
    #然后在view中获得数据
    $data = gen2();
    foreach ($data as $key => $value) {
        handleView($key, $value);
    }

通过以上的代码,我们将几个获取数据的操作都延迟到了数据被渲染的时候执行。节省了中间进行其他操作时获取回来的数据占用的内存空间。然而实际开放项目的过程中,这些数据往往被多处使用。而且这样的结构让我们单独控制数据变得艰难,以此带来的性能提升相对于便利性来说,好处微乎其微。不过还好的是,我们对yield的了解才刚刚到一半,已经有这样的功效了。相信我们在了解完另外一半之后,它的功效将大大提升。
接下来我们来继续了解yield, 由于yield返回的是一个Generator类的对象,这个对象除了实现了Iterator接口之外,内部还有一个相当重要的方法就是send方法,即我们提到的第6点特性,通过send方法我们可以给yield发送一个值作为yield语句的值。


首先大家考虑一下下面的代码

    function gen3(){
        echo "test\n";
        echo (yield 1)."I\n";
        echo (yield 2)."II\n";
        echo (yield 3 + 1)."III\n";
    }
    $gen = gen3();
    foreach ($gen as $key => $value) {
        echo "{$key} - {$value}\n";
    }

执行以后输出
    0 - 1
    I
    1 - 2
    II
    2 - 4
    III

可能这段输出比较难理解,我们接下来,一步一步分析一下为什么得出这样的输入。由于我们知道了foreach的时候gen内部是如何操作的,那么我们便用代码来实现一次。

    $gen = gen3();
    $gen->rewind();
    echo $gen->key().' - '.$gen->current()."\n"; 
    $gen->next();

执行后输出
    0 - 1
    I

通过这两句我们发现,当前的key为0,current则为1也就是yield后面表达式的值。因为yield 1被括号括起来了,所以yield后面表达式的值是1,如果没有括号则为1."I\n".当然因为1."I\n"是一个错误语法。如果想要测试的朋友需要给1加上双引号。
当执行next时,第1个yield到第二个yieldz之间的的语法被执行。也就是echo (yield 1)."I\n"被执行了,由于我们使用的是next(),所以yield当前是无值的。所以输出了I。需要注意的是在第一个yield之后的语法将不会被执行,而 echo (yield 2). "II\n";属于下一个yield块的语句,所以不会被执行。
到这里,是时候让我们今天最后的主角send方法来表现一下了。

public mixed Generator::send ( mixed $value ) 
这个是手册里send方法的描述,可以看出来他可以接受一个mixed类型的参数,也会返回一个mixed类型的值。
传入的参数会被做 yield 关键字在语句中的值,而他的返回值则是next之后,$gen->current()的值。

下面我们来尝试一下

    $gen = gen3(); 
    $gen->rewind();
    echo $gen->key().' - '.$gen->current()."\n"; 
    echo $gen->send("send value - ");  

执行后输出
    0 - 1
    send value - I
    2
这时候我们发现,我们通过send方法成功的将一个值传递给了一个函数的内部,并且当做yield关键字的值给输出了,由于下一个yield的值为2,所以我们调用send返回的值为2,同样被输出。

虽然我们知道了send可以完成内部对函数内部的yield表达式传值,也知道了可以通过$gen->current()获得当前yield表达式之后的值,但是这个有什么用呢。可以看一下这个函数

复制代码
    function gen4(){
        $id = 2;
        $id = yield $id;
        echo $id;
    }

    $gen = gen4();
    $gen->send($gen->current() + 3);
复制代码

根据上面对yield代码的理解,我们不难发现这个函数会输出5,因为current()为2,而当我们send之后 yield的值为 2 + 3,也就是5.同时yield到函数结束之间的代码被执行。也就是$id = 5; echo $id;
通过这样一个简单的例子,我们发现。我们不但从函数内部获得了返回值,并且将他的返回值再次发送给了函数内部参与后续的计算。

转载于:https://www.cnblogs.com/sgm4231/p/9778556.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值