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 函数,依 ...
随机推荐
- 5.List链表类型介绍和操作
数据类型List链表 (1)介绍 list类型其实就是一个双向链表.通过push,pop操作从链表的头部或者尾部添加删除元素.这使得list既可以用作栈,也可以用作队列. 该list链表类型应用场景: ...
- Ant标签详解--基础操作
Ant的一些核心概念: build.xml:构建文件是以XML 文件来描述的,默认构建文件名为build.xml. project:每个构建文件包含一个工程. property:属性,一 ...
- Dell KACE K1000 poc
POST /service/krashrpt.php HTTP/1.1 Host: xxx.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x ...
- 记一次使用windbg排查内存泄漏的过程
一.背景 近期有一个项目在运行当中出现一些问题,程序顺利启动,但是观察一阵子后发现内存使用总量在很缓慢地升高, 虽然偶尔还会往下降一些,但是总体还是不断上升:内存运行6个小时候从33M上升到80M: ...
- 【JVM】关于OOM的二三事
组织架构 严格来说,StackOverflowError和OutOfMemoryError都属于错误,而不是异常. java.lang.StackOverflowError public class ...
- python初学者笔记(2):阿拉伯数字转换成中文大写
题:输入一个数字,转换成中文大写的写法 可运行的程序(Python 2.7.9): # -*- coding: utf-8 -*- #在python2的py文件里面写中文,必须要添加一行声明文件编码的 ...
- Linux (九)服务器环境搭建
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.行为守则 1.拍摄快照 Linux系统操作非常复杂,搭建开发环境时全部使用命令完成安装过程.而在Li ...
- Linux (五) VIM编辑器
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.简介 Linux系统环境下的一款非常重要的文本编辑工具,我们在Linux环境下几乎所有的文本文件 ...
- Java实现 LeetCode 558 四叉树交集(四叉树,第一次遇到,研究了半天)
558. 四叉树交集 四叉树是一种树数据,其中每个结点恰好有四个子结点:topLeft.topRight.bottomLeft 和 bottomRight.四叉树通常被用来划分一个二维空间,递归地将其 ...
- Java实现 LeetCode 67 二进制求和
67. 二进制求和 给定两个二进制字符串,返回他们的和(用二进制表示). 输入为非空字符串且只包含数字 1 和 0. 示例 1: 输入: a = "11", b = "1 ...