天天看点

NodeJS总结(四):yield、return与柯里化

对于ES6的生成器函数总结有四点:

1. yield必须放置在*函数中;

2. 每次执行到yield时都会暂停函数中剩余代码的执行;

3. *函数必须通过函数调用的方式(new方式会报错)才能产生自身的实例,并且每个实例都互相独立;

4. 一个生成器函数一旦迭代完成,则再也无法还原,一直停留在最后一个位置;

尤其是第二点,是非常强大的功能,暂停代码执行,以前只有在浏览器环境中,alert、comfirm等系统内置函数才具有类似的能力,所以如果熟悉多线程的语言,你会找到类似的感觉,于是也有人说,有了yield,NodeJS就有协程的能力,完全可以处理多个需要协作的任务。

也是因为第二点,生成器函数也具有惰性求值的特性,针对这一特性,我们可以很容易写出科里化函数进行惰性求值,如下:

function *curr() {
    let items = [],
        value
    do {
        //  如果不需要返回值,可以直接写成value = yield
        //  每次返回现有的数组元素
        value = yield items.slice()
        //  当等于-1时终止
        if(value !== -) {
            items.push(value)
        }
    } while(value !== -)
    //  进行求值
    let sum = 
    items.forEach(item => sum = sum + item)
    yield sum 
}
           

根据生成器函数的第二个特性,只要函数中还有yield未执行,那么剩余的代码就绝不会执行,所以上面的代码中,只要循环未完成,求值的代码就不会执行,再看相关的测试代码,如下:

let curring = curr()
//  必须要空转一次,代码启动柯里化
curring.next()
curring.next()
curring.next()
curring.next()
//  启动求值过程
console.log(curring.next(-))
           

上面的代码有一次空转的过程,这是因为next方法的参数只可以作为上一个yield表达式的返回值,所以对所有的生成器函数而言,都无法获取到第一次的next()方法的返回值,具体的执行过程如下:

1. curring.next()空转时,执行到yield items.slice()暂停,请记住,此时还没有返回值,而且value的赋值操作还没有执行,请记住赋值语句的右侧代码先执行;

2. 继续执行curring.next(1),返回值为1,进行赋值操作,并继续剩余的循环代码,直到遇到yield才终止本次执行;

3. 继续执行第二步,直到方法结束为止;

在上面的执行结果中,我们还发现输出结果有些出乎意料,如下:

{ value: , done: false }
           

done竟然是false,这说明方法还没有执行结束,必须还要执行一次curring.next,才能终结方法,done才能变为true,这实在是太低效了,又空转了一次,那怎么改进呢?很简单,只需要将最后的yield改为return即可,如下:

//  进行求值
let sum = 
items.forEach(item => sum = sum + item)
//  只有使用return才能终结方法
return sum 
           

这是yield与return的区别:

1. yield仅代表本次迭代完成,并且还必有下一次迭代;

2. return则代表生成器函数完成;

最后,为了减少柯里化代码中不必要的一次空转迭代,我们用一种掩耳盗铃的方式封装构造函数,为什么是掩耳盗铃,一看就明白:

//  对生成器函数进行封装
function wrapper(fn) {
    //  这里的...运算符将参数转换为数组
    return function(...args) {
        //  除了使用析构与扩展运算,还可以使用apply函数,如下
        //let generator = fn.apply(null, args)
        //  这里的...运算符是逆向运算,将数组又转换为参数列表
        let generator =  fn(...args)
        //  将空转放到这里
        generator.next()
        return generator;
    }
}
//  现在应用代码可以简化了
let curring = wrapper(curr)()
curring.next()
curring.next()
curring.next()
           

总结

利用生成器函数可以进行惰性求值,但无法获取到第一次next函数传入的值,而且只要执行了yield的返回操作,那么构造函数一定没有执行完成,除非遇到了显式的return语句。

继续阅读