Thunk函数的使用
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函数的使用的更多相关文章
- JavaScript 中的 Thunk 函数
参数的求值策略: var x = 1; function f(m){ return m * 2; } f(x + 5); // x +5 在何时运算? 1.传值调用: var x = 1; funct ...
- 转: ES6异步编程:Thunk函数的含义与用法
转: ES6异步编程:Thunk函数的含义与用法 参数的求值策略 Thunk函数早在上个世纪60年代就诞生了. 那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好.一个争论的焦点是&quo ...
- js-学习笔记-Thunk函数
Thunk 函数是自动执行 Generator 函数的一种方法. 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体.这个临时函数就叫做 Thunk 函数. fun ...
- thunk函数
1.函数参数求值的策略 a.传值策略(c语言) 传值策略就是在进入函数体之前将 参数计算之后 将参数的值传入到函数体之中. let x = 8 f(x + 1)//参数为 f(9)//传进去的值实际上 ...
- thunk 函数
function* f() { console.log(1); for (var i = 0; true; i++) { console.log('come in'); var reset = yie ...
- async 函数学习笔记
async函数就是Generator函数的语法糖. var fs = require('fs'); var readFile = function (fileName) { return new Pr ...
- 转: ES6异步编程: co函数库的含义与用法
转: ES6异步编程: co函数库的含义与用法 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行. 比如,有一个 Ge ...
- ECMAScript 6 学习(二)async函数
1.什么是async函数 2.用法 2.1基本用法 3.语法 3.1返回promise对象 3.2promise状态的变化 3.3await命令 1.什么是async函数 async函数也是异步编程 ...
- ES2017中的async函数
前面的话 ES2017标准引入了 async 函数,使得异步操作变得更加方便.本文将详细介绍async函数 概述 async 函数是 Generator 函数的语法糖 使用Generator 函数,依 ...
随机推荐
- StreamSets使用指南
StreamSets使用指南 最近在调研Streamsets,照猫画虎做了几个最简单的Demo鉴于网络上相关资料非常少,做个记录. 1.简介 Streamsets是一款大数据实时采集和ETL工具,可以 ...
- 【Ubuntu】利用sudo修改/etc/sudoers翻车
翻车背景:利用命令行创建新用户,这里不得不提该翻车博客[1],当然这里并没有怪罪的意思,贴出来只是为了让后来者使用正确命令修改sudoers文件.系统:Ubuntu18.04 利用[1]中的“新用 ...
- UPX的使用
UPX是一个通用可执行文件压缩器,由于其具有: 压缩率高:压缩效果优于zip/gzip: 解压速度快:在奔腾133上即可达到大约10MB/秒: 压缩的可执行文件没有额外的内存开销: 安全:可以列表,检 ...
- Rocket - util - Timer
https://mp.weixin.qq.com/s/Z4JJhZ_jL1lqF1nf_orq9A 简单介绍Timer的实现. 1. 基本功能 实现定时器的功能. 2. Ti ...
- 【JSP】el、jstl、MVC、三层架构
JSP[Java Server Pages] [概念] java服务器页面 简化的servlet设计 html语法中扩展java 跨平台 JSP全名为Java Server Pages,中文名叫jav ...
- Java实现 蓝桥杯VIP 算法训练 FBI树
问题描述 我们可以把由"0"和"1"组成的字符串分为三类:全"0"串称为B串,全"1"串称为I串,既含"0&q ...
- Java实现 LeetCode 221 最大正方形
221. 最大正方形 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积. 示例: 输入: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 ...
- Java实现N的N次幂
如果x的x次幂结果为10(参见[图1.png]),你能计算出x的近似值吗? 显然,这个值是介于2和3之间的一个数字. 请把x的值计算到小数后6位(四舍五入),并填写这个小数值. 注意:只填写一个小数, ...
- java实现海盗比酒量
** 海盗比酒量** 有一群海盗(不多于20人),在船上比拼酒量.过程如下:打开一瓶酒,所有在场的人平分喝下,有几个人倒下了.再打开一瓶酒平分,又有倒下的,再次重复- 直到开了第4瓶酒,坐着的已经所剩 ...
- java实现第六届蓝桥杯立方尾不变
立方尾不变 立方尾不变 有些数字的立方的末尾正好是该数字本身. 比如:1,4,5,6,9,24,25,- 请你计算一下,在10000以内的数字中(指该数字,并非它立方后的数值),符合这个特征的正整数一 ...