溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JS Thunk 函數(shù)的含義和用法實(shí)例總結(jié)

發(fā)布時(shí)間:2020-09-05 16:15:03 來(lái)源:腳本之家 閱讀:202 作者:李小強(qiáng) 欄目:web開(kāi)發(fā)

本文實(shí)例講述了JS Thunk 函數(shù)的含義和用法。分享給大家供大家參考,具體如下:

前面我們已經(jīng)學(xué)習(xí)過(guò)了Generator 函數(shù)的優(yōu)勢(shì)和使用場(chǎng)景。

這篇文章我們繼續(xù)學(xué)習(xí)阮老師的第二篇文章,Thunk 函數(shù)的含義和用法

說(shuō)實(shí)話,在這之前是沒(méi)聽(tīng)過(guò)這個(gè)詞的,但其實(shí)如果你對(duì)犀牛書(shū)里的不完全函數(shù)有認(rèn)真看過(guò)的話
理解起來(lái)也不是很費(fèi)勁。

首先什么是 thunk 函數(shù)?

很多場(chǎng)景下我們都會(huì)陷入一個(gè)問(wèn)題,就是函數(shù)參數(shù)的求值時(shí)間。

是函數(shù)調(diào)用時(shí)即求值還是在函數(shù)內(nèi)使用時(shí)才求值?

var x = 1;
function f(m){
 return m * 2;   
}
f(x + 5)
//我們把在調(diào)用時(shí)就計(jì)算的方式稱(chēng)為傳值調(diào)用,等同于:
f(6)
//我們把在函數(shù)內(nèi)部使用時(shí)才求值的方式稱(chēng)為傳名調(diào)用,等同于:
return (x + 5) * 2;

兩種方式各有利弊,傳值調(diào)用比較簡(jiǎn)單,但是如果計(jì)算后的結(jié)果沒(méi)有在程序中使用的話,損失就有點(diǎn)大。
因此有很多場(chǎng)景都傾向于傳名調(diào)用。

但是像 C,java 的編譯方式都是固定的,如何基于現(xiàn)有基礎(chǔ)改變程序的執(zhí)行方式。

比較常見(jiàn)的是將想要傳名調(diào)用的參數(shù)放到一個(gè)臨時(shí)函數(shù)之中,把臨時(shí)函數(shù)當(dāng)做參數(shù),只在使用的時(shí)候執(zhí)行。

這個(gè)包裝參數(shù)的臨時(shí)函數(shù)就叫 Thunk 函數(shù)。我們?cè)囈幌掠?Thunk 函數(shù)改寫(xiě)一下上面的例子:

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

其實(shí)這里我倒覺(jué)得可以翻翻犀牛書(shū)里的不完全函數(shù),跟 Thunk 函數(shù)一個(gè)道理,

通過(guò) return 一個(gè) function 來(lái)實(shí)現(xiàn)傳名調(diào)用。

老師也順便介紹了用在生產(chǎn)環(huán)境的 Thunkify 模塊

我們看一下源碼,還是有一些好玩的地方的。

function thunkify(fn){
 //全局返回一個(gè)臨時(shí)函數(shù)
 return function(){
  var args = new Array(arguments.length);
  var ctx = this;
 
  for(var i = 0; i < args.length; ++i) {
   args[i] = arguments[i];
  }
  //上面一段將參數(shù)copy到args
  
  return function(done){
   var called; 
   args.push(function(){
    if (called) return; //對(duì)callback重新包裝,控制callback只執(zhí)行一次
    called = true;
    done.apply(null, arguments);
   });
 
   try {
    fn.apply(ctx, args);
   } catch (err) {
    done(err);
   }
  }
 }
};

執(zhí)行結(jié)果:

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

這個(gè)地方的理解,方法f在執(zhí)行時(shí)的參數(shù)并不是 1,2,console.log

console.log 參數(shù)在 thunkify 內(nèi)部被重新包裝,成了:

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

了解了 Thunk 函數(shù)之后,我們要停下來(lái)想一想,還是那句話,它的出現(xiàn)要解決什么問(wèn)題?

是不是一定要使用 Thunk 函數(shù)?Thunk 用在什么場(chǎng)景下?

從前面的內(nèi)容來(lái)看,其實(shí)并沒(méi)有什么用,可能概念比較新穎,但是使用起來(lái)好像并沒(méi)有太多提高。

但是沒(méi)用的話我們也不會(huì)寫(xiě)這么一篇文章。

自從有了 Generator 函數(shù),Thunk 函數(shù)現(xiàn)在可以用于 Generator 函數(shù)的自動(dòng)流程管理。

看一下例子:

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());
};

這個(gè)例子中,我們使用 yield 將執(zhí)行權(quán)交給下一個(gè)協(xié)程,那么就需要有一種方法把執(zhí)行權(quán)在交還給當(dāng)前函數(shù)

這種方法就是 Thunk 函數(shù),因?yàn)樗梢灾匦掳b回調(diào)函數(shù),我們可以自己寫(xiě)包裝函數(shù),將執(zhí)行權(quán)交還給 Generator 函數(shù)。

為了對(duì)比,我們先看一下如果手動(dòng)執(zhí)行上面的代碼會(huì)是什么樣的:

var g = gen();         //開(kāi)始執(zhí)行協(xié)程
var r1 = g.next();       //讀取第一個(gè)文件
r1.value(function(err, data){  //讀取完成執(zhí)行回調(diào)
 if (err) throw err;
 var r2 = g.next(data);    //讀取第二個(gè)文件
 r2.value(function(err, data){ //讀取完成執(zhí)行回調(diào)
  if (err) throw err;
  g.next(data);        //結(jié)束協(xié)程
 });
});

不難發(fā)現(xiàn),上面的代碼其實(shí)就是將同一個(gè)回調(diào)函數(shù)傳入 value 屬性(next 執(zhí)行返回 value 和 done )

我在看的時(shí)候就在想,這個(gè)value是屬性啊,為什么可以執(zhí)行?還傳遞參數(shù)?

慢慢理一理:

value屬性是yield的返回值,gen中的yield返回的是一個(gè) Thunk 函數(shù),不是固定值。

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

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

傳遞的function回調(diào)等同于(console.log);

這么是不是就理解了?

Thunk 函數(shù)真正的威力,在于可以自動(dòng)執(zhí)行 Generator 函數(shù)。下面就是一個(gè)基于 Thunk 函數(shù)的 Generator 執(zhí)行器:

function run(fn) {
 var gen = fn();  //自動(dòng)開(kāi)始協(xié)程
 //對(duì)next進(jìn)行包裝,形成 Thunk 函數(shù),遍歷調(diào)用
 function next(err, data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }
 next();
 /* 參考bootstrap的寫(xiě)法改寫(xiě)一下
 !function next(err, data) {
  var result = gen.next(data);
  if (result.done) return;
  result.value(next);
 }();
 */
}
run(gen);

上面的寫(xiě)法很簡(jiǎn)單吧,這么改寫(xiě)之后就不需要你手動(dòng)的去控制執(zhí)行next的時(shí)機(jī)了

只需要執(zhí)行run函數(shù)就行。但是要保證每一個(gè)yield后面都是一個(gè) Thunk 函數(shù),否則的話就不能自動(dòng)執(zhí)行了。

這一章的學(xué)習(xí)總結(jié)就結(jié)束了,我們學(xué)會(huì)了如何使用 Thunk 函數(shù)實(shí)現(xiàn)自動(dòng)執(zhí)行,但 Thunk 函數(shù)并不是 Generator 函數(shù)自動(dòng)執(zhí)行的唯一方案。

因?yàn)樽詣?dòng)執(zhí)行的關(guān)鍵是,必須有一種機(jī)制,自動(dòng)控制 Generator 函數(shù)的流程,接收和交還程序的執(zhí)行權(quán)?;卣{(diào)函數(shù)可以做到這一點(diǎn),Promise 對(duì)象也可以做到這一點(diǎn)。

下一篇文章我們?nèi)タ匆幌禄趐romise實(shí)現(xiàn)的自動(dòng)執(zhí)行器:co

原文:Thunk 函數(shù)的含義和用法

感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運(yùn)行工具:http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行效果。

更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專(zhuān)題:《JavaScript常用函數(shù)技巧匯總》、《javascript面向?qū)ο笕腴T(mén)教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》

希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI