搜尋
首頁web前端js教程詳細介紹JavaScript函數柯里化的一些思考

1. 高階函數的坑

在學習柯里化之前,我們先來看看下面一段程式碼:

var f1 = function(x){
    return f(x);
};
f1(x);

很多同學都能看出來,這些寫是非常愚蠢的,因為函數f1f是等效的,我們直接令var f1 = f;就行了,完全沒有必要包裹那麼一層。

但是,下面一段程式碼就未必能夠看得出問題來了:

var getServerStuff = function(callback){
  return ajaxCall(function(json){
    return callback(json);
  });
};

這是我摘自《JS函數式程式設計指南》中的一段程式碼,實際上,利用上面的規則,我們可以得到callback與函數

function(json){return callback(json);};

是等價的,所以函數可以化簡為:

var getServerStuff = function(callback){
  return ajaxCall(callback);
};

繼續化簡:

var getServerStuff = ajaxCall;

如此一來,我們發現那麼長一段程式都白寫了。

函數既可以當參數,又可以當回傳值,是高階函數的重要特性,但是稍不留神就容易踩到坑裡。

2. 函數柯里化(curry)

言歸正傳,什麼是函數柯里化?函數柯里化(curry)就是只傳遞給函數一部分參數來呼叫它,讓它回傳一個函數去處理剩下的參數。聽得很繞口,其實很簡單,其實就是將函數的變數分開來呼叫:f(x,y,z) -> f(x)(y)(z)

對於最開始的例子,按照如下實現,要傳入兩個參數,f1呼叫方式是f1(f,x)

var f1 = function(f,x){
    return f(x);
};

注意,由於f是作為一個函數變數傳入,所以f1變成了一個新的函數。

我們將f1改變一下,利用閉包可以寫成如下形式,則f1呼叫方式變成了f1( f)(x),而且得到的結果完全一樣。這就完成了f1的柯里化。

var f1 = function(f){
    return function(x){
        return f(x);
    }
};
var f2 = f1(f);
f2(x);

其實這個例子舉得不恰當,細心的同學可能會發現,f1雖然是一個新函數,但是f2f#是完全等效的,繞了半天,還是繞回來了。

這裡有一個很經典的例子:

['11', '11', '11'].map(parseInt) //[ 11, NaN, 3 ]
['11', '11', '11'].map(f1(parseInt)) //[ 11, 11, 11 ]

由於parseInt接受兩個參數,所以直接呼叫會有進位轉換的問題,參考「不願相離」的文章。

var f2 = f1(parseInt)f2parseInt由原來的接受兩個參數變成了只接受一個參數的新函數,從而解決這個進制轉換問題。透過我們的f1包裹以後就能夠運行出正確的結果了。

有同學覺得這個不算柯里化的應用,我覺得還是算吧,各位同學可以一起來討論下。

3. 函數柯里化進一步思考

如果說上一節的例子中,我們不是直接運行f(x),而是把函數f當做一個參數,結果會怎麼樣呢?我們來看下面這個範例:

假設f1傳回函數gg的作用域指向xs,函數f作為g的參數。最後我們可以寫成如下形式:

var f1 = function(f,xs){
    return g.call(xs,f);
};

實際上,用f1來取代g.call(xxx)的做法叫反柯里化。例如:

var forEach = function(xs,f){
    return Array.prototype.forEach.call(xs,f);
};
var f = function(x){console.log(x);};
var xs = {0:'peng',1:'chen',length:2};
forEach(xs,f);

反curring就是把原來已經固定的參數或this上下文等當作參數延遲到未來傳遞。
它能夠在很大程度上簡化函數,前提是你得習慣它。

拋開反柯里化,如果我們要柯里化f1怎麼辦?

使用閉包,我們可以寫成如下形式:

var f1 = function(f){
    return function(xs){
        return g.call(xs,f);
    }
};
var f2 = f1(f);
f2(xs);

f傳入f1中,我們就可以得到f2這個新函數。

只傳給函數一部分參數通常也叫做局部呼叫(partial application),能夠大量減少樣板檔案程式碼(boilerplate code)。

當然,函數f1傳入的兩個參數不一定要包含函數+非函數,可能兩個都是函數,也可能兩個都是非函數。

我個人覺得柯里化並非是必須的,而且不熟悉的同學閱讀起來可能會遇到麻煩,但是它能幫助我們理解JS中的函數式編程,更重要的是,我們以後在閱讀類似的程式碼時,不會感到陌生。知乎上羅納同學講的挺好:

並非「柯里化」對函數式程式設計有意義。而是,函數式程式設計在把函數當作一等公民的同時,就不可避免的會產生「柯里化」這種用法。所以它並不是因為「有什麼意義」才出現的。當然既然存在了,我們自然可以探討怎麼利用這個現象。

練習:

// 通过局部调用(partial apply)移除所有参数
var filterQs = function(xs) {
  return filter(function(x){ return match(/q/i, x);  }, xs);
};
//这两个函数原题没有,是我自己加的
var filter = function(f,xs){
    return xs.filter(f);
};
var match = function(what,x){
    return x.match(what);
};

分析:函數filterQs的作用是:傳入一個字串數組,過濾出包含'q'的字串,並組成一個新的數組回傳。

我們可以透過以下步驟得到函數filterQs

a. filter传入的两个参数,第一个是回调函数,第二个是数组,filter主要功能是根据回调函数过滤数组。我们首先将filter函数柯里化:

var filter = function(f){
    return function (xs) {
        return xs.filter(f);
    }
};

b. 其次,filter函数传入的回调函数是matchmatch的主要功能是判断每个字符串是否匹配what这个正则表达式。这里我们将match也柯里化:

var match = function(what){
    return function(x){
        return x.match(what);
    }
};
var match2 = match(/q/i);

创建匹配函数match2,检查字符串中是否包含字母q。

c. 把match2传入filter中,组合在一起,就形成了一个新的函数:

var filterQs =  filter(match2);
var xs = ['q','test1','test2'];
filterQs(xs);

从这个示例中我们也可以体会到函数柯里化的强大。所以,柯里化还有一个重要的功能:封装不同功能的函数,利用已有的函数组成新的函数。

4. 函数柯里化的递归调用

函数柯里化还有一种有趣的形式,就是函数可以在闭包中调用自己,类似于函数递归调用。如下所示:

function add( seed ) {
    function retVal( later ) {
        return add( seed + later );
    }
    retVal.toString = function() {
        return seed;
    };
    return retVal;
}
console.log(add(1)(2)(3).toString()); // 6

add函数返回闭包retVal,在retVal中又继续调用add,最终我们可以写成add(1)(2)(3)(...)这样柯里化的形式。
关于这段代码的解答,知乎上的李宏训同学回答地很好:

每调用一次add函数,都会返回retValue函数;调用retValue函数会调用add函数,然后还是返回retValue函数,所以调用add的结果一定是返回一个retValue函数。add函数的存在意义只是为了提供闭包,这个类似的递归调用每次调用add都会生成一个新的闭包。

5. 函数组合(compose)

函数组合是在柯里化基础上完成的:

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};
var f1 = compose(f,g);
f1(x);

将传入的函数变成两个,通过组合的方式返回一个新的函数,让代码从右向左运行,而不是从内向外运行。

函数组合和柯里化有一个好处就是pointfree。

pointfree 模式指的是,永远不必说出你的数据。它的意思是说,函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。

// 非 pointfree,因为提到了数据:name
var initials = function (name) {
  return name.split(' ').map(compose(toUpperCase, head)).join('. ');
};

// pointfree
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");
// 'H. S. T'

以上是詳細介紹JavaScript函數柯里化的一些思考的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器