1. 程式人生 > 程式設計 >JS Thunk 函式的含義和用法例項總結

JS Thunk 函式的含義和用法例項總結

本文例項講述了JS Thunk 函式的含義和用法。分享給大家供大家參考,具體如下:

前面我們已經學習過了Generator 函式的優勢和使用場景。

這篇文章我們繼續學習阮老師的第二篇文章,Thunk 函式的含義和用法

說實話,在這之前是沒聽過這個詞的,但其實如果你對犀牛書裡的不完全函式有認真看過的話
理解起來也不是很費勁。

首先什麼是 thunk 函式?

很多場景下我們都會陷入一個問題,就是函式引數的求值時間。

是函式呼叫時即求值還是在函式內使用時才求值?

var x = 1;
function f(m){
 return m * 2;   
}
f(x + 5)
//我們把在呼叫時就計算的方式稱為傳值呼叫,等同於:
f(6)
//我們把在函式內部使用時才求值的方式稱為傳名呼叫,等同於:
return (x + 5) * 2;

兩種方式各有利弊,傳值呼叫比較簡單,但是如果計算後的結果沒有在程式中使用的話,損失就有點大。
因此有很多場景都傾向於傳名呼叫。

但是像 C,java 的編譯方式都是固定的,如何基於現有基礎改變程式的執行方式。

比較常見的是將想要傳名呼叫的引數放到一個臨時函式之中,把臨時函式當做引數,只在使用的時候執行。

這個包裝引數的臨時函式就叫 Thunk 函式。我們試一下用 Thunk 函式改寫一下上面的例子:

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

其實這裡我倒覺得可以翻翻犀牛書裡的不完全函式,跟 Thunk 函式一個道理,

通過 return 一個 function 來實現傳名呼叫。

老師也順便介紹了用在生產環境的 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];
  }
  //上面一段將引數copy到args
  
  return function(done){
   var called; 
   args.push(function(){
    if (called) return; //對callback重新包裝,控制callback只執行一次
    called = true;
    done.apply(null,arguments);
   });
 
   try {
    fn.apply(ctx,args);
   } catch (err) {
    done(err);
   }
  }
 }
};

執行結果:

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

這個地方的理解,方法f在執行時的引數並不是 1,2,console.log

console.log 引數在 thunkify 內部被重新包裝,成了:

function(){
 if (called) return; //對callback重新包裝,控制callback只執行一次
 called = true;
 console.log.apply(null,arguments);
}

瞭解了 Thunk 函式之後,我們要停下來想一想,還是那句話,它的出現要解決什麼問題?

是不是一定要使用 Thunk 函式?Thunk 用在什麼場景下?

從前面的內容來看,其實並沒有什麼用,可能概念比較新穎,但是使用起來好像並沒有太多提高。

但是沒用的話我們也不會寫這麼一篇文章。

自從有了 Generator 函式,Thunk 函式現在可以用於 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 將執行權交給下一個協程,那麼就需要有一種方法把執行權在交還給當前函式

這種方法就是 Thunk 函式,因為它可以重新包裝回調函式,我們可以自己寫包裝函式,將執行權交還給 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);        //結束協程
 });
});

不難發現,上面的程式碼其實就是將同一個回撥函式傳入 value 屬性(next 執行返回 value 和 done )

我在看的時候就在想,這個value是屬性啊,為什麼可以執行?還傳遞引數?

慢慢理一理:

value屬性是yield的返回值,gen中的yield返回的是一個 Thunk 函式,不是固定值。

所以可以執行value,看前面例子裡的這句:ft(1,2)(console.log);

value就等同於ft(1,2)的返回值

傳遞的function回撥等同於(console.log);

這麼是不是就理解了?

Thunk 函式真正的威力,在於可以自動執行 Generator 函式。下面就是一個基於 Thunk 函式的 Generator 執行器:

function run(fn) {
 var gen = fn();  //自動開始協程
 //對next進行包裝,形成 Thunk 函式,遍歷呼叫
 function next(err,data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }
 next();
 /* 參考bootstrap的寫法改寫一下
 !function next(err,data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }();
 */
}
run(gen);

上面的寫法很簡單吧,這麼改寫之後就不需要你手動的去控制執行next的時機了

只需要執行run函式就行。但是要保證每一個yield後面都是一個 Thunk 函式,否則的話就不能自動執行了。

這一章的學習總結就結束了,我們學會了如何使用 Thunk 函式實現自動執行,但 Thunk 函式並不是 Generator 函式自動執行的唯一方案。

因為自動執行的關鍵是,必須有一種機制,自動控制 Generator 函式的流程,接收和交還程式的執行權。回撥函式可以做到這一點,Promise 物件也可以做到這一點。

下一篇文章我們去看一下基於promise實現的自動執行器:co

原文:Thunk 函式的含義和用法

感興趣的朋友可以使用線上HTML/CSS/JavaScript程式碼執行工具:http://tools.jb51.net/code/HtmlJsRun測試上述程式碼執行效果。

更多關於JavaScript相關內容可檢視本站專題:《JavaScript常用函式技巧彙總》、《javascript面向物件入門教程》、《JavaScript錯誤與除錯技巧總結》、《JavaScript資料結構與演算法技巧總結》及《JavaScript數學運算用法總結》

希望本文所述對大家JavaScript程式設計有所幫助。