一.起源

传值调用or传名调用

var x = 1;
function f(m){return m * 2;
}
f(x + 5)
// 一种意见是"传值调用"(call by value),即在进入函数体之前,就计算 x + 5 的值(等于6),再将这个值传入函数 f 。C语言就采用这种策略。
// 另一种意见是"传名调用"(call by name),即直接将表达式 x + 5 传入函数体,只在用到它的时候求值。Hskell语言采用这种策略。

传值调用和传名调用,哪一种比较好?回答是各有利弊。传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。

function f(a, b){return b;
}
f(3 * x * x - 2 * x - 1, x);

上面的函数f的第一个参数值是一个复杂的计算,但是实际在函数的调用中,并没有使用这个值,如果是传值调用,就会造成极大的性能浪费。

二.Thunk函数

编译器的传名调用,往往是把参数表达式放在一个临时的函数中,再把这个临时函数当做参数传入函数体,这个临时函数就是Thunk函数。

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

三.Javascript中的Thunk函数

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);// Thunk版本的readFile(单参数版本)
var thunkFn = Thunk(filename);
thunkFn(cb)
var Thunk = function(filename) {return function(cb) {return fs.readFile(fileName, cb)}
}

上面代码中,fs 模块的 readFile 方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数。

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。


var Thunk = function(fn){return function (){var args = Array.prototype.slice.call(arguments);return function (callback){args.push(callback);return fn.apply(this, args);}};
};

使用上面的转换器,生成 fs.readFile 的 Thunk 函数。


var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

四、Thunkify 模块

生产环境的转换器,建议使用 Thunkify 模块。

首先是安装。


$ npm install thunkify

使用方式如下。


var thunkify = require('thunkify');
var fs = require('fs');var read = thunkify(fs.readFile);
read('package.json')(function(err, str){// ...
});

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];}return function(done){var called;args.push(function(){if (called) return;called = true;done.apply(null, arguments);});try {fn.apply(ctx, args);} catch (err) {done(err);}}}
};

它的源码主要多了一个检查机制,变量 called 确保回调函数只运行一次。这样的设计与下文的 Generator 函数相关。请看下面的例子。


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

上面代码中,由于 thunkify 只允许回调函数执行一次,所以只输出一行结果。

五、Generator 函数的流程管理

你可能会问, Thunk 函数有什么用?回答是以前确实没什么用,但是 ES6 有了 Generator 函数,Thunk 函数现在可以用于 Generator 函数的自动流程管理。

以读取文件为例。下面的 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 命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。

这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数。为了便于理解,我们先看如何手动执行上面这个 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);});
});

上面代码中,变量 g 是 Generator 函数的内部指针,表示目前执行到哪一步。next 方法负责将指针移动到下一步,并返回该步的信息(value 属性和 done 属性)。

仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入 next 方法的 value 属性。这使得我们可以用递归来自动完成这个过程。

六、Thunk 函数的自动流程管理

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


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

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

有了这个执行器,执行 Generator 函数方便多了。不管有多少个异步操作,直接传入 run 函数即可。当然,前提是每一个异步操作,都要是 Thunk 函数,也就是说,跟在 yield 命令后面的必须是 Thunk 函数。


var gen = function* (){var f1 = yield readFile('fileA');var f2 = yield readFile('fileB');// ...var fn = yield readFile('fileN');
};run(gen);

上面代码中,函数 gen 封装了 n 个异步的读取文件操作,只要执行 run 函数,这些操作就会自动完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。

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

深入理解thunk函数相关推荐

  1. 彻底理解thunk函数与co框架

    ES6带来了很多新的特性,其中生成器.yield等能对之前金字塔式的异步回调做到很好地解决,而基于此封装的co框架能让我们完全已同步的方式来编写异步代码.这篇文章就对生成器函数(GeneratorFu ...

  2. Thunk 函数的含义和用法

    一.参数的求值策略 Thunk函数早在上个世纪60年代就诞生了. 那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好.一个争论的焦点是"求值策略",即函数的参数到底应该 ...

  3. es6 --- Thunk函数的作用

    首先了解一下javascript里面的Thunk函数的含义:将多参数函数,替换成一个只接受回调函数作为参数的单参数函数 // 一个具体的例子// 正常版本的readFile(多参数函数) fs.rea ...

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

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

  5. 深入理解javascript函数系列第二篇——函数参数

    前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...

  6. C++深入理解虚函数

    c++深入理解虚函数 虚函数的使用方法: (1)在基类用virtual声明成员函数为虚函数.这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便被调用. 在类外定义虚函数时,不必在定义vir ...

  7. 【Python】深入理解Python函数的9个黄金法则

    编程离不开函数.Python的函数除了具备传统意义上的函数特征外,又被赋予了其他一些特性,让它更灵活.更强大.这篇文章结合之前我推荐的一本Python宝书,又添加一些我的实践和理解,总结了深入理解Py ...

  8. python函数可以作为容器对象吗_正确理解Python函数是第一类对象

    正确理解 Python函数,能够帮助我们更好地理解 Python 装饰器.匿名函数(lambda).函数式编程等高阶技术. 函数(Function)作为程序语言中不可或缺的一部分,太稀松平常了.但函数 ...

  9. 正确理解Python函数是第一类对象

    正确理解 Python函数,能够帮助我们更好地理解 Python 装饰器.匿名函数(lambda).函数式编程等高阶技术. 函数(Function)作为程序语言中不可或缺的一部分,太稀松平常了.但函数 ...

最新文章

  1. kafka常用的命令
  2. jupyter kernel_jupyter增加多版本python内核
  3. android menu item属性,menuitem - Android 4.3菜单项showAsAction =“always”被忽略
  4. centos7.3部署django用uwsgi和nginx[亲测可用]
  5. 与神经网络相比,你对P图一无所知
  6. flashlite3无法接入网络的解决办法
  7. java 进度条实现原理_java进度条功能的实现原理是什么?实例展示
  8. 腾讯会议共享屏幕 共享PPT视频声音
  9. 【华为机试真题 JAVA】最大股票收益-100
  10. 录播网站 服务器,录播服务器
  11. firefox浏览器一分钟去广告--去广告插件安装教程(adblock plus)
  12. 再给大家推荐一些小游戏
  13. 关于谷歌邮箱注册收不到验证码——”此电话号码无法用于验证“的问题
  14. 安装算量软件消火栓系统_识别其他设备
  15. ApkScan-PKID查壳工具+脱壳(搬运)
  16. Salesforce诊断网络问题以排除性能下降
  17. 上顿号符号_在电脑上打标点符号,顿号怎样打出来?
  18. B树索引,面向海量数据的金钥匙
  19. 软考复习-软件工程05-软件项目估算进度管理
  20. [附源码]java毕业设计中小企业人事管理系统

热门文章

  1. div上下左右居中方法
  2. java作品设计报告书_java实验报告书格式模板.doc
  3. 12-移动端技术选型
  4. 003 模拟类之肺活量传感器
  5. 什么是UI设计中的情感化设计?
  6. php 如何用op浏览器开发手机网站,关于开发手机网站的一些总结
  7. LKD (linux内核设计与实现)笔记
  8. pytorch 中 contiguous() 函数理解
  9. 单链表的面试题——Java数据结构
  10. 简洁,漂亮实用的万年历