8KOBE24

Thunk函数的使用

编译器的求值策略通常分为传值调用以及传名调用,Thunk函数是应用于编译器的传名调用实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk 函数。

求值策略#

编译器的求值策略通常分为传值调用以及传名调用,在下面的例子中,将一个表达式作为参数进行传递,传值调用以及传名调用中实现的方式有所不同。

 

Copy

var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1);

在上述的例子中,无论是使用传值调用还是使用传名调用,执行的结果都是一样的,但是其调用过程不同:

  • 传值调用:首先计算x + 1,然后将计算结果2传递到s函数,即相当于调用s(2)
  • 传名调用:直接将x + 1表达式传递给y,使用时再计算x + 1,即相当于计算(x + 1) + 1

传值调用与传名调用各有利弊,传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成没有必要的计算。传名调用可以解决这个问题,但是实现相对来说比较复杂。

 

Copy

var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1, x + 2);

在上面这个例子中,函数s并没有用到x + 2这个表达式求得的值,使用传名调用的话只将表达式传入而并未计算,只要在函数中没有用到x + 2这个表达式就不会计算,使用传值调用的话就会首先将x + 2的值计算然后传入,如果没有用到这个值,那么就多了一次没有必要的计算。Thunk函数就是作为传名调用的实现而构建的,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk 函数。

 

Copy

var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1); // 等同于 var x = 1; function s(thunk){ console.log(thunk() + 1); // 3 } var thunk = function(){ return x + 1; } s(thunk);

Js中的Thunk函数#

Js中的求值策略是是传值调用,在Js中使用Thunk函数需要手动进行实现且含义有所不同,在Js中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

 

Copy

// 假设一个延时函数需要传递一些参数 // 通常使用的版本如下 var delayAsync = function(time, callback, ...args){ setTimeout(() => callback(...args), time); } var callback = function(x, y, z){ console.log(x, y, z); } delayAsync(1000, callback, 1, 2, 3); // 使用Thunk函数 var thunk = function(time, ...args){ return function(callback){ setTimeout(() => callback(...args), time); } } var callback = function(x, y, z){ console.log(x, y, z); } var delayAsyncThunk = thunk(1000, 1, 2, 3); delayAsyncThunk(callback);

实现一个简单的Thunk函数转换器,对于任何函数,只要参数有回调函数,就能写成Thunk函数的形式。

 

Copy

var convertToThunk = function(funct){ return function (...args){ return function (callback){ return funct.apply(this, args); } }; }; var callback = function(x, y, z){ console.log(x, y, z); } var delayAsyncThunk = convertToThunk(function(time, ...args){ setTimeout(() => callback(...args), time); }); thunkFunct = delayAsyncThunk(1000, 1, 2, 3); thunkFunct(callback);

Thunk函数在ES6之前可能应用比较少,但是在ES6之后,出现了Generator函数,通过使用Thunk函数就可以可以用于Generator函数的自动流程管理。首先是关于Generator函数的基本使用,调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器iterator对象,他是一个指向内部状态对象的指针。当这个迭代器的next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值,也就是指针就会从函数头部或者上一次停下来的地方开始执行到下一个yield。或者如果用的是yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

 

Copy

function* f(x) { yield x + 10; yield x + 20; return x + 30; } var g = f(1); console.log(g); // f {<suspended>} console.log(g.next()); // {value: 11, done: false} console.log(g.next()); // {value: 21, done: false} console.log(g.next()); // {value: 31, done: true} console.log(g.next()); // {value: undefined, done: true} // 可以无限next(),但是value总为undefined,done总为true

由于Generator函数能够将函数的执行暂时挂起,那么他就完全可以操作一个异步任务,当上一个任务完成之后再继续下一个任务,下面这个例子就是将一个异步任务同步化表达,当上一个延时定时器完成之后才会进行下一个定时器任务,可以通过这种方式解决一个异步嵌套的问题,例如利用回调的方式需要在一个网络请求之后加入一次回调进行下一次请求,很容易造成回调地狱,而通过Generator函数就可以解决这个问题,事实上async/await就是利用的Generator函数以及Promise实现的异步解决方案。

 

Copy

var it = null; function f(){ var rand = Math.random() * 2; setTimeout(function(){ if(it) it.next(rand); },1000) } function* g(){ var r1 = yield f(); console.log(r1); var r2 = yield f(); console.log(r2); var r3 = yield f(); console.log(r3); } it = g(); it.next();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值