子曰:到底什么是thunk?
thunk、thunkify、甚至React 还有一个叫redux-thunk,为什么都有一个thunk 呢?在通读《You Dont Know JS》 时,看到了作者Kyle Simposn对thunk 的解释,对thunk 有了一定的了解,同时参考了阮一峰的 Thunk 函数的含义和用法,通过对比将其摘录下来。
Thunk 的历史
20 世纪60 年代在编程语言刚起步时,计算机科学家研究编译器怎么写比较好(注意,是编译器而不是JavaScript)。一个争议点是“求值策略”,即函数的参数到底应该合适求值。
1  | var x = 1  | 
x + 5 表达式合适求值?
1. 传值调用(call by value)
在进入函数体之前,就计算x + 5 的值(等于6),再将这个值传入函数foo。C 语言就采用这种策略。
1  | foo(x + 5)  | 
2. 传名调用(call by name)
直接将表达式x + 5 传入函数体,只在用到它的时候求值。Haskell 语言采用这种策略。
1  | foo(x + 5)  | 
编译器的“传名调用” 实现,往往是将参数放到一个临时函数中,在将这个临时函数传入函数体。这个临时函数就叫做Thunk 函数。
1  | var thunk = function () {  | 
Thunk 函数是“传名调用” 的一种实现策略,用来替换某个表达式。
在JavaScript 语言中,Thunk 函数替换的不是表达式,而是多惨函数,将其替换程单参数的版本,且只接受回调函数作为参数
上述来自Thunk 函数的含义和用法 阮一峰。
下面则是来自你不了解的JS Thunks
JavaScript 语言的Thunk
狭隘的定义:
thunk是一个JS 函数–没有任何参数–它连接并调用另一个函数。扩展之后的定义:
thunk是一个JS 函数–接受一个回调–它连接并调用另一个函数。
对狭隘的定义来说,你用一个函数定义包装函数调用–带着它需要的所有参数–来推迟这个调用的执行,而这个包装用的函数就是thunk。当你稍后执行thunk 时,你最终会调用那个原始的函数。
下面代码中的fooThunk 就是thunk 函数。
1  | function foo (x, y) {  | 
而一个接受回调函数的thunk 是为了处理一个异步的thunk 而产生的。
1  | function foo (x, y, cb) {  | 
fooThunk(..) 仅需要一个cb(..) 参数。因为它已经预先制定
了值3 和4(分别为x 和y)并准备传递给foo(..)。一个thunk 只是在外面耐心地等待着它开始工作所需的最后一部分信息:回调。
Thunkify
使用工具生成thunk,而不是手动编码。
1  | function thunkify (fn) {  | 
前面的thunkify() 接受foo(..) 函数的引用,和任何它所需的参数,并返回thunk 本身(fooThunk(..))。然而,这不是你将在JS 中发现的thunk 的典型表达方式。
与thunkify(..) 制造thunk 本身相反,典型的thunkify(..) 工具将产生一个制造thunk 的函数。
1  | function thunkify (fn) {  | 
不同之处是有一个额外的return function () {..}。用法也不一样。
1  | var whatIsThis = thunkify(foo)  | 
whatIsThis 不是thunk,它是一种thunk 的“工厂”,可以称之为thunkory(thunk + factory)。于是thunkify(..) 制造了一个thunkory,而一个thunkory 制造thunk。
一般来讲,在程序的一开始就制造一些thunkory 来包装既存API 的方法是十分有用的,然后就可以在需要thunk 的时候传递并调用这些thunkory。这两个分开的步骤保证了功能上更干净的分离。
1  | // 更干净  |