Thunk函数的使用

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

求值策略

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

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

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

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 函数。

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函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

// 假设一个延时函数需要传递一些参数
// 通常使用的版本如下
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函数的形式。

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*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

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实现的异步解决方案。

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();

虽然上边的例子能够自动执行,但是不够方便,现在实现一个Thunk函数的自动流程管理,其自动帮我们进行回调函数的处理,只需要在Thunk函数中传递一些函数执行所需要的参数比如例子中的index,然后就可以编写Generator函数的函数体,通过左边的变量接收Thunk函数中funct执行的参数,在使用Thunk函数进行自动流程管理时,必须保证yield后是一个Thunk函数。

关于自动流程管理run函数,首先需要知道在调用next()方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量,在这个函数中,第一次执行next时并未传递参数,而且在第一个yield上边也并不存在接收变量的语句,无需传递参数,接下来就是判断是否执行完这个生成器函数,在这里并没有执行完,那么将自定义的next函数传入res.value中,这里需要注意res.value是一个函数,可以在下边的例子中将注释的那一行执行,然后就可以看到这个值是f(funct){...},此时我们将自定义的next函数传递后,就将next的执行权限交予了f这个函数,在这个函数执行完异步任务后,会执行回调函数,在这个回调函数中会触发生成器的下一个next方法,并且这个next方法是传递了参数的,上文提到传入参数后会将其传递给上一条执行的yield语句左边的变量,那么在这一次执行中会将这个参数值传递给r1,然后在继续执行next,不断往复,直到生成器函数结束运行,这样就实现了流程的自动管理。

function thunkFunct(index){
return function f(funct){
var rand = Math.random() * 2;
setTimeout(() => funct({rand:rand, index: index}), 1000)
}
} function* g(){
var r1 = yield thunkFunct(1);
console.log(r1.index, r1.rand);
var r2 = yield thunkFunct(2);
console.log(r2.index, r2.rand);
var r3 = yield thunkFunct(3);
console.log(r3.index, r3.rand);
} function run(generator){
var g = generator(); var next = function(data){
var res = g.next(data);
if(res.done) return ;
// console.log(res.value);
res.value(next);
} next();
} run(g);

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/9302a1d01113
https://segmentfault.com/a/1190000017211798
http://www.ruanyifeng.com/blog/2015/05/thunk.html

Thunk函数的使用的更多相关文章

  1. JavaScript 中的 Thunk 函数

    参数的求值策略: var x = 1; function f(m){ return m * 2; } f(x + 5); // x +5 在何时运算? 1.传值调用: var x = 1; funct ...

  2. 转: ES6异步编程:Thunk函数的含义与用法

    转: ES6异步编程:Thunk函数的含义与用法 参数的求值策略 Thunk函数早在上个世纪60年代就诞生了. 那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好.一个争论的焦点是&quo ...

  3. js-学习笔记-Thunk函数

    Thunk 函数是自动执行 Generator 函数的一种方法. 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体.这个临时函数就叫做 Thunk 函数. fun ...

  4. thunk函数

    1.函数参数求值的策略 a.传值策略(c语言) 传值策略就是在进入函数体之前将 参数计算之后 将参数的值传入到函数体之中. let x = 8 f(x + 1)//参数为 f(9)//传进去的值实际上 ...

  5. thunk 函数

    function* f() { console.log(1); for (var i = 0; true; i++) { console.log('come in'); var reset = yie ...

  6. async 函数学习笔记

    async函数就是Generator函数的语法糖. var fs = require('fs'); var readFile = function (fileName) { return new Pr ...

  7. 转: ES6异步编程: co函数库的含义与用法

    转: ES6异步编程: co函数库的含义与用法 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行. 比如,有一个 Ge ...

  8. ECMAScript 6 学习(二)async函数

     1.什么是async函数 2.用法 2.1基本用法 3.语法 3.1返回promise对象 3.2promise状态的变化 3.3await命令 1.什么是async函数 async函数也是异步编程 ...

  9. ES2017中的async函数

    前面的话 ES2017标准引入了 async 函数,使得异步操作变得更加方便.本文将详细介绍async函数 概述 async 函数是 Generator 函数的语法糖 使用Generator 函数,依 ...

随机推荐

  1. java远程执行linux服务器上的shell脚本

    业务场景:需要从服务器A中新增的文件同步至本地服务器,服务器A中内存有限,需同步成功之后清除文件. Java调用远程shell脚本,需要和远程服务器建立ssh链接,再调用指定的shell脚本. 1.创 ...

  2. pytest常用命令参数

    pytest 参数 1.参数:-s 运行过程中执行print打印函数:pytest -s,以下两个输出 上边带参数,下边不带 2.参数: --collect-only 收集将要执行的用例,但不会执行用 ...

  3. 前端基础进阶(十一):详细图解jQuery对象,以及如何扩展jQuery插件

    早几年学习前端,大家都非常热衷于研究jQuery源码.我还记得当初从jQuery源码中学到一星半点应用技巧的时候常会有一种发自内心的惊叹,“原来JavaScript居然可以这样用!” 虽然随着前端的发 ...

  4. Java集合(九)哈希冲突及解决哈希冲突的4种方式

    Java集合(九)哈希冲突及解决哈希冲突的4种方式 一.哈希冲突 (一).产生的原因 哈希是通过对数据进行再压缩,提高效率的一种解决方法.但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致 ...

  5. Rocket - diplomacy - IdRange

    https://mp.weixin.qq.com/s/qqL2XWqAhVcnGSxs6kxhLg   介绍IdRange的实现.   ​​   1. 基本定义   A non-empty half- ...

  6. (Java实现) 洛谷 P1051 谁拿了最多奖学金

    题目描述 某校的惯例是在每学期的期末考试之后发放奖学金.发放的奖学金共有五种,获取的条件各自不同: 院士奖学金,每人8000元,期末平均成绩高于80分(>80),并且在本学期内发表1篇或1篇以上 ...

  7. Java实现 LeetCode 112 路径总和

    112. 路径总和 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标 ...

  8. 第三届蓝桥杯C++B组国(决)赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.星期几 1949年的国庆节(10月1日)是星期六. 今年(2012)的国庆节是星期一. 那么,从建国到现在,有几次国庆节正好是星期日呢 ...

  9. 第八届蓝桥杯JavaB组省赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.购物单 题目描述 小明刚刚找到工作,老板人很好,只是老板夫人很爱购物.老板忙的时候经常让小明帮忙到商场代为购物.小明很厌烦,但又不好推 ...

  10. 第九届蓝桥杯JavaB组省赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.第几天 题目描述 2000年的1月1日,是那一年的第1天. 那么,2000年的5月4日,是那一年的第几天? 注意:需要提交的是一个整数 ...