子曰:到底什么是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 | // 更干净 |