Javascript函数参数求值——Thunk函数

问题发现

我们在平常的开发过程中可能会写出以下类型的代码:

...
fun(x+1,2); //参数是一个表达式的情况
...
复制代码

诸如此类的参数是一个表达式,那我们时候考虑过一个问题,这个参数究竟是在什么时候求的值呢?

两种情况

我们通过下面的这个函数来讲解两种情况

var a = 1;

function double(argument){
    return argument * 2;
}


double(a+1);
复制代码
传值调用(call by value)

就是在参数进入函数体之前就把表达式的值计算出来在代入函数体。类似于C语言

double(a+1);
//当使用传值调用时,上面的语句等同于
double(2);
复制代码
传名调用(call by name)

直接把表达式传入函数体,当需要用到它的时候再运算它求值。类似于Scala

double(a+1);
//当使用传名调用时,上面的语句等同于
(a+1) * 2;
复制代码
利弊分析

传值调用 相比较于 传名调用 比较简单,但是对参数求值的时候如果我们实际并没有用到这个参数可能会造成性能的损失。

function fun(a,b){
    if(a == 1){
        return b;
    }else{
        return a;
    }
}
var x = 100;
fun(0,x * 3 * 10 - x * 20 + x);//注意此时传递进去的第二个参数因为条件判断后并没有使用
复制代码

上面的代码中,因为 a 并不等于 1 ,所以这次函数我们并没有用到第二个参数,但是如果我们要求值就需要计算那一长串表达式,实际上是没有必要的,所以我们更倾向于传名调用这种高效率的参数求值方式。

But, what a pity! JavaScript语言使用的方式是传值调用,但我们可以用一种其他方法来实现传名调用——Thunk函数

Thunk函数

编译器进行传名调用的方法就是先将参数放到一个临时函数中,再将这个临时函数传入函数体。这个临时函数就是主角 —— Thunk函数。

function double(argument){
    return argument * 2;
}

double(a+1);
//等同于
var thunk = function(){ //thunk临时函数
    return a + 1;
}
function double(thunk){
    return thunk() * 2;
}
复制代码

相当于是当我们需要用的那个参数里表达式的值的时候就调用那个函数求值即可。它是实现传名调用的一种策略。

在Javascript中实现Thunk函数

对于Javascript这门语言来说,它的Thunk函数更加特殊。它替换的不是表达式而是多参数函数,把它变成一个-只接受回调函数-作为参数-的-单参数函数。

// ES6版本
var Thunk = function(fun) {  //一个thunk函数转换器
  return function (...args) {
    return function (callback) {
      return fn.call(this, ...args, callback);
    }
  };
};
复制代码

使用转换器我们可以把一个需要引入多个回调函数的函数变为一个thunk函数。

var multipleParametersFunc = function(param,callback){
    console.log(param);
    callback();
}
//使用转换器
var thunkFunc = Thunk(multipleParametersFunc);
thunkFunc(param)(callback);
复制代码

Thunk的强大功能——Generator函数的自动流程管理

Thunk函数在ES6的出现之后又有了一个强大的功能——对Generator函数的自动流程管理。

在不使用Thunk函数时我们可以用如下方法自动执行Generator

function* gen(){
    //do something...
}
var g = gen();
var result = g.next();

while(!result.done){
    console.log(result.value);
    result = g.next();
}
复制代码

下面我们使用了Thunk函数自动执行

function thunkRunGen(fun){
    var gen = fun();
    
    function next(err, data){
        var result = gen.next(data);
        if(result.done) return;
        result.value(next);
    }
    
    next();
}

function* g(){
    //do something
}

thunkRunGen(g);
复制代码

上面代码的thunkRunGen函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

使用这个执行器,不管Generator函数内部有多少个异步操作,我们只需要把Generator函数传入thunkRunGen即可。但有个前提,每个yield 函数后面都需要是Thunk函数。

Thunk 函数它并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是-自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象当然也可以做到这一点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值