JS Thunk 函数的含义和用法实例总结


Posted in Javascript onApril 08, 2020

本文实例讲述了JS Thunk 函数的含义和用法。分享给大家供大家参考,具体如下:

前面我们已经学习过了Generator 函数的优势和使用场景。

这篇文章我们继续学习阮老师的第二篇文章,Thunk 函数的含义和用法

说实话,在这之前是没听过这个词的,但其实如果你对犀牛书里的不完全函数有认真看过的话
理解起来也不是很费劲。

首先什么是 thunk 函数?

很多场景下我们都会陷入一个问题,就是函数参数的求值时间。

是函数调用时即求值还是在函数内使用时才求值?

var x = 1;
function f(m){
 return m * 2;   
}
f(x + 5)
//我们把在调用时就计算的方式称为传值调用,等同于:
f(6)
//我们把在函数内部使用时才求值的方式称为传名调用,等同于:
return (x + 5) * 2;

两种方式各有利弊,传值调用比较简单,但是如果计算后的结果没有在程序中使用的话,损失就有点大。
因此有很多场景都倾向于传名调用。

但是像 C,java 的编译方式都是固定的,如何基于现有基础改变程序的执行方式。

比较常见的是将想要传名调用的参数放到一个临时函数之中,把临时函数当做参数,只在使用的时候执行。

这个包装参数的临时函数就叫 Thunk 函数。我们试一下用 Thunk 函数改写一下上面的例子:

function f(m){
 return m * 2;   
}
 
f(x + 5);
 
// 等同于
 
var thunk = function () {
 return x + 5;
};
 
function f(thunk){
 return thunk() * 2;
}

其实这里我倒觉得可以翻翻犀牛书里的不完全函数,跟 Thunk 函数一个道理,

通过 return 一个 function 来实现传名调用。

老师也顺便介绍了用在生产环境的 Thunkify 模块

我们看一下源码,还是有一些好玩的地方的。

function thunkify(fn){
 //全局返回一个临时函数
 return function(){
  var args = new Array(arguments.length);
  var ctx = this;
 
  for(var i = 0; i < args.length; ++i) {
   args[i] = arguments[i];
  }
  //上面一段将参数copy到args
  
  return function(done){
   var called; 
   args.push(function(){
    if (called) return; //对callback重新包装,控制callback只执行一次
    called = true;
    done.apply(null, arguments);
   });
 
   try {
    fn.apply(ctx, args);
   } catch (err) {
    done(err);
   }
  }
 }
};

执行结果:

function f(a, b, callback){
 var sum = a + b;
 callback(sum);
 callback(sum);
}
 
var ft = thunkify(f);
ft(1, 2)(console.log); 
// 3

这个地方的理解,方法f在执行时的参数并不是 1,2,console.log

console.log 参数在 thunkify 内部被重新包装,成了:

function(){
 if (called) return; //对callback重新包装,控制callback只执行一次
 called = true;
 console.log.apply(null, arguments);
}

了解了 Thunk 函数之后,我们要停下来想一想,还是那句话,它的出现要解决什么问题?

是不是一定要使用 Thunk 函数?Thunk 用在什么场景下?

从前面的内容来看,其实并没有什么用,可能概念比较新颖,但是使用起来好像并没有太多提高。

但是没用的话我们也不会写这么一篇文章。

自从有了 Generator 函数,Thunk 函数现在可以用于 Generator 函数的自动流程管理。

看一下例子:

var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);
 
var gen = function* (){
 var r1 = yield readFile('/etc/fstab');
 console.log(r1.toString());
 var r2 = yield readFile('/etc/shells');
 console.log(r2.toString());
};

这个例子中,我们使用 yield 将执行权交给下一个协程,那么就需要有一种方法把执行权在交还给当前函数

这种方法就是 Thunk 函数,因为它可以重新包装回调函数,我们可以自己写包装函数,将执行权交还给 Generator 函数。

为了对比,我们先看一下如果手动执行上面的代码会是什么样的:

var g = gen();         //开始执行协程
var r1 = g.next();       //读取第一个文件
r1.value(function(err, data){  //读取完成执行回调
 if (err) throw err;
 var r2 = g.next(data);    //读取第二个文件
 r2.value(function(err, data){ //读取完成执行回调
  if (err) throw err;
  g.next(data);        //结束协程
 });
});

不难发现,上面的代码其实就是将同一个回调函数传入 value 属性(next 执行返回 value 和 done )

我在看的时候就在想,这个value是属性啊,为什么可以执行?还传递参数?

慢慢理一理:

value属性是yield的返回值,gen中的yield返回的是一个 Thunk 函数,不是固定值。

所以可以执行value,看前面例子里的这句:ft(1, 2)(console.log);

value就等同于ft(1, 2)的返回值

传递的function回调等同于(console.log);

这么是不是就理解了?

Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器:

function run(fn) {
 var gen = fn();  //自动开始协程
 //对next进行包装,形成 Thunk 函数,遍历调用
 function next(err, data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }
 next();
 /* 参考bootstrap的写法改写一下
 !function next(err, data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }();
 */
}
run(gen);

上面的写法很简单吧,这么改写之后就不需要你手动的去控制执行next的时机了

只需要执行run函数就行。但是要保证每一个yield后面都是一个 Thunk 函数,否则的话就不能自动执行了。

这一章的学习总结就结束了,我们学会了如何使用 Thunk 函数实现自动执行,但 Thunk 函数并不是 Generator 函数自动执行的唯一方案。

因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。

下一篇文章我们去看一下基于promise实现的自动执行器:co

原文:Thunk 函数的含义和用法

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
Javascript 中文字符串处理额外注意事项
Nov 15 Javascript
原生javascript实现获取指定元素下所有后代元素的方法
Oct 28 Javascript
Javascript调用函数方法的几种方式介绍
Mar 20 Javascript
Angularjs全局变量被作用域监听的正确姿势
Feb 06 Javascript
基于JS实现textarea中获取动态剩余字数的方法
May 25 Javascript
js中获取时间new Date()的全面介绍
Jun 20 Javascript
Javascript中类式继承和原型式继承的实现方法和区别之处
Apr 25 Javascript
jQuery制作全屏宽度固定高度轮播图(实例讲解)
Jul 08 jQuery
angularJs使用ng-repeat遍历后选中某一个的方法
Sep 30 Javascript
JavaScript函数定义方法实例详解
Mar 05 Javascript
关于vue3.0中的this.$router.replace({ path: '/'})刷新无效果问题
Jan 16 Javascript
Vue打包部署到Nginx时,css样式不生效的解决方式
Aug 03 Javascript
JS Generator 函数的含义与用法实例总结
Apr 08 #Javascript
Vue列表循环从指定下标开始的多种解决方案
Apr 08 #Javascript
《javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解
Apr 08 #Javascript
vue开发移动端底部导航条功能
Apr 08 #Javascript
《javascript设计模式》学习笔记五:Javascript面向对象程序设计工厂模式实例分析
Apr 08 #Javascript
vue实现表单未编辑或未保存离开弹窗提示功能
Apr 08 #Javascript
JS快速实现简单计算器
Apr 08 #Javascript
You might like
用文本文件实现的动态实时发布新闻的程序
2006/10/09 PHP
Joomla框架实现字符串截取的方法示例
2017/07/18 PHP
js中将多个语句写成一个语句的两种方法小结
2007/12/08 Javascript
jquery.boxy弹出框(后隔N秒后自动隐藏/自动跳转)
2013/01/15 Javascript
input输入框的自动匹配(原生代码)
2013/03/19 Javascript
js将控件隐藏的方法及display属性介绍
2013/07/04 Javascript
JS 数字转换研究总结
2013/12/26 Javascript
JavaScript中实现PHP的打乱数组函数shuffle实例
2014/10/11 Javascript
jQuery Uploadify 上传插件出现Http Error 302 错误的解决办法
2015/12/12 Javascript
AngularJS仿苹果滑屏删除控件
2016/01/18 Javascript
Javascript中的Prototype到底是什么
2016/02/16 Javascript
Google 地图获取API Key详细教程
2016/08/06 Javascript
Javascript 制作图形验证码实例详解
2016/12/22 Javascript
jQuery的三种bind/One/Live/On事件绑定使用方法
2017/02/23 Javascript
BootStrap Validator 根据条件在JS中添加或移除校验操作
2017/10/12 Javascript
webpack实现一个行内样式px转vw的loader示例
2018/09/13 Javascript
JS复杂判断的更优雅写法代码详解
2018/11/07 Javascript
[01:29:46]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第二局
2016/02/27 DOTA
[01:07:21]NAVI vs VG Supermajor 败者组 BO3 第二场 6.5
2018/06/06 DOTA
[01:11:37]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第一场 11.19
2020/11/19 DOTA
Python实现将数据库一键导出为Excel表格的实例
2016/12/30 Python
python使用itchat实现手机控制电脑
2018/02/22 Python
Python3 使用cookiejar管理cookie的方法
2018/12/28 Python
Python异常模块traceback用法实例分析
2019/10/22 Python
Python实现快速大文件比较代码解析
2020/09/04 Python
python从PDF中提取数据的示例
2020/10/30 Python
html5使用canvas画一条线
2014/12/15 HTML / CSS
html5使用canvas绘制太阳系效果
2014/12/15 HTML / CSS
amazeui 验证按钮扩展的实现
2020/08/21 HTML / CSS
Spartoo西班牙官网:法国时尚购物网站
2018/03/27 全球购物
澳大利亚优质葡萄酒专家:Vintage Cellars
2019/01/08 全球购物
音乐教育感言
2014/03/05 职场文书
井冈山红色之旅心得体会
2014/10/07 职场文书
同意迁入证明模板
2014/10/26 职场文书
2014年汽车销售工作总结
2014/12/01 职场文书
一文简单了解MySQL前缀索引
2022/04/03 MySQL