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 相关文章推荐
js滚动条多种样式,推荐
Feb 05 Javascript
jquery库或JS文件在eclipse下报错问题解决方法
Apr 17 Javascript
addEventListener 的用法示例介绍
May 07 Javascript
Javascript基础教程之if条件语句
Jan 18 Javascript
jquery实现图片上传之前预览的方法
Jul 11 Javascript
四种参数传递的形式——URL,超链接,js,form表单
Jul 24 Javascript
jquery获取input type=text中的值的各种方式(总结)
Dec 02 Javascript
Django中使用jquery的ajax进行数据交互的实例代码
Oct 15 jQuery
实例详解ztree在vue项目中使用并且带有搜索功能
Aug 24 Javascript
使用jQuery如何写一个含验证码的登录界面
May 13 jQuery
javascript 关于赋值、浅拷贝、深拷贝的个人理解
Nov 01 Javascript
vue实现日历表格(element-ui)
Sep 24 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
PHP实现通过正则表达式替换回调的内容标签
2015/06/15 PHP
PHP创建word文档的方法(平台无关)
2016/03/29 PHP
PHP MSSQL 分页实例
2016/04/13 PHP
php数据访问之查询关键字
2016/05/09 PHP
PHP自定义递归函数实现数组转JSON功能【支持GBK编码】
2018/07/17 PHP
PHP chop()函数讲解
2019/02/11 PHP
Mootools 1.2教程 Fx.Tween的使用
2009/09/15 Javascript
js 弹出菜单/窗口效果
2011/10/30 Javascript
jquery Mobile入门—多页面切换示例学习
2013/01/08 Javascript
js中的cookie的读写操作示例详解
2014/04/17 Javascript
JavaScript高级程序设计(第三版)学习笔记6、7章
2016/03/11 Javascript
JavaScript数据绑定实现一个简单的 MVVM 库
2016/04/08 Javascript
JS定时器使用,定时定点,固定时刻,循环执行详解
2016/05/31 Javascript
Bootstrap基本样式学习笔记之图片(6)
2016/12/07 Javascript
基于bootstrap实现收缩导航条
2017/03/17 Javascript
详解用node.js实现简单的反向代理
2017/06/26 Javascript
jQuery实现简单的下拉菜单导航功能示例
2017/12/07 jQuery
微信小程序新手教程之启动页的重要性
2019/03/03 Javascript
在Layui中操作数据表格,给指定单元格添加事件示例
2019/10/26 Javascript
[01:37]全新的一集《真视界》——TI7总决赛
2017/09/21 DOTA
Python操作MySQL简单实现方法
2015/01/26 Python
python 查找文件名包含指定字符串的方法
2018/06/05 Python
Python实现的爬取百度贴吧图片功能完整示例
2019/05/10 Python
五个2015 年最佳HTML5 框架
2015/11/11 HTML / CSS
Avène雅漾美国官方网站:敏感肌肤护理专家
2016/10/24 全球购物
世界上最悠久的自行车制造商:Ribble Cycles
2017/03/18 全球购物
Staples英国官方网站:办公用品一站式采购
2017/10/06 全球购物
应届生法律求职信
2013/10/22 职场文书
《盲人摸象》教学反思
2014/02/16 职场文书
大学生个人自荐信
2014/02/24 职场文书
企业文化口号
2014/06/12 职场文书
动漫设计与制作专业推荐信
2014/07/07 职场文书
环保志愿者活动方案
2014/08/14 职场文书
2014单位领导班子四风对照检查材料思想汇报
2014/09/25 职场文书
公司员工离职证明书
2014/10/04 职场文书
2016父亲节感恩话语
2015/12/09 职场文书