日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js 高阶函数之柯里化

發(fā)布時(shí)間:2023/12/10 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js 高阶函数之柯里化 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

博客地址:https://ainyi.com/74

定義

在計(jì)算機(jī)科學(xué)中,柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)

就是只傳遞給函數(shù)某一部分參數(shù)來調(diào)用,返回一個(gè)新函數(shù)去處理剩下的參數(shù)(==閉包==)

常用的封裝成 add 函數(shù)

// reduce 方法 const add = (...args) => args.reduce((a, b) => a + b)// 傳入多個(gè)參數(shù),執(zhí)行 add 函數(shù) add(1, 2) // 3// 假設(shè)有一個(gè) currying 函數(shù) let sum = currying(params) sum(1)(3) // 4

實(shí)際應(yīng)用

延遲計(jì)算

部分求和例子,說明了延遲計(jì)算的特點(diǎn)

const add = (...args) => args.reduce((a, b) => a + b)// 簡化寫法 function currying(func) {const args = []return function result(...rest) {if (rest.length === 0) {return func(...args)} else {args.push(...rest)return result}} }const sum = currying(add)sum(1, 2)(3) // 未真正求值,收集參數(shù)的和 sum(4) // 未真正求值,收集參數(shù)的和 sum() // 輸出 10

上面的代碼理解:先定義 add 函數(shù),然后 currying 函數(shù)就是用==閉包==把傳入?yún)?shù)保存起來,當(dāng)傳入?yún)?shù)的數(shù)量足夠執(zhí)行函數(shù)時(shí),就開始執(zhí)行函數(shù)

上面的 currying 函數(shù)是一種簡化寫法,判斷傳入的參數(shù)長度是否為 0,若為 0 執(zhí)行函數(shù),否則收集參數(shù)到 args 數(shù)組

另一種常見的應(yīng)用是 bind 函數(shù),我們看下 bind 的使用

let obj = {name: 'Krry' } const fun = function () {console.log(this.name) }.bind(obj)fun() // Krry

這里 bind 用來改變函數(shù)執(zhí)行時(shí)候的上下文==this==,但是函數(shù)本身并不執(zhí)行,所以本質(zhì)上是延遲計(jì)算,這一點(diǎn)和 call / apply 直接執(zhí)行有所不同

動(dòng)態(tài)創(chuàng)建函數(shù)

有一種典型的應(yīng)用情景是這樣的,每次調(diào)用函數(shù)都需要進(jìn)行一次判斷,但其實(shí)第一次判斷計(jì)算之后,后續(xù)調(diào)用并不需要再次判斷,這種情況下就非常適合使用柯里化方案來處理

即第一次判斷之后,動(dòng)態(tài)創(chuàng)建一個(gè)新函數(shù)用于處理后續(xù)傳入的參數(shù),并返回這個(gè)新函數(shù)。當(dāng)然也可以使用惰性函數(shù)來處理,本例最后一個(gè)方案會(huì)介紹

我們看下面的這個(gè)例子,在 DOM 中添加事件時(shí)需要兼容現(xiàn)代瀏覽器和 IE 瀏覽器(IE < 9),方法就是對(duì)瀏覽器環(huán)境進(jìn)行判斷,看瀏覽器是否支持,簡化寫法如下

// 簡化寫法 function addEvent (type, el, fn, capture = false) {if (window.addEventListener) {el.addEventListener(type, fn, capture);}else if(window.attachEvent) {el.attachEvent('on' + type, fn);} }

但是這種寫法有一個(gè)問題,就是每次添加事件都會(huì)調(diào)用做一次判斷,比較麻煩

可以利用閉包和立即調(diào)用函數(shù)表達(dá)式(IIFE)來實(shí)現(xiàn)只判斷一次,后續(xù)都無需判斷

const addEvent = (function(){if (window.addEventListener) {return function (type, el, fn, capture) { // 關(guān)鍵el.addEventListener(type, fn, capture)}}else if(window.attachEvent) {return function (type, el, fn) { // 關(guān)鍵el.attachEvent('on' + type, fn)}} })()

上面這種實(shí)現(xiàn)方案就是一種典型的柯里化應(yīng)用,在第一次的 if...else if... 判斷之后完成第一次計(jì)算,然后動(dòng)態(tài)創(chuàng)建返回新的函數(shù)用于處理后續(xù)傳入的參數(shù)

這樣做的好處就是之后調(diào)用之后就不需要再次調(diào)用計(jì)算了

當(dāng)然可以使用惰性函數(shù)來實(shí)現(xiàn)這一功能,原理很簡單,就是重寫函數(shù)

function addEvent (type, el, fn, capture = false) {// 重寫函數(shù)if (window.addEventListener) {addEvent = function (type, el, fn, capture) {el.addEventListener(type, fn, capture);}}else if(window.attachEvent) {addEvent = function (type, el, fn) {el.attachEvent('on' + type, fn);}}// 執(zhí)行函數(shù),有循環(huán)爆棧風(fēng)險(xiǎn)addEvent(type, el, fn, capture); }

第一次調(diào)用 addEvent 函數(shù)后,會(huì)進(jìn)行一次環(huán)境判斷,在這之后 addEvent 函數(shù)被重寫,所以下次調(diào)用時(shí)就不會(huì)再次判斷環(huán)境

參數(shù)復(fù)用

我們知道調(diào)用 toString() 可以獲取每個(gè)對(duì)象的類型,但是不同對(duì)象的 toString() 有不同的實(shí)現(xiàn)

所以需要通過 Object.prototype.toString() 來獲取 Object 上的實(shí)現(xiàn)

同時(shí)以 call() / apply() 的形式來調(diào)用,并傳遞要檢查的對(duì)象作為第一個(gè)參數(shù)

例如下面這個(gè)例子

function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }function isNumber(obj) {return Object.prototype.toString.call(obj) === '[object Number]'; }function isString(obj) {return Object.prototype.toString.call(obj) === '[object String]'; }// Test isArray([1, 2, 3]) // true isNumber(123) // true isString('123') // true

但是上面方案有一個(gè)問題,那就是每種類型都需要定義一個(gè)方法,這里我們可以使用 bind 來擴(kuò)展,優(yōu)點(diǎn)是可以直接使用改造后的 toStr

const toStr = Function.prototype.call.bind(Object.prototype.toString);// 改造前直接調(diào)用 [1, 2, 3].toString() // "1,2,3" '123'.toString() // "123" 123.toString() // SyntaxError: Invalid or unexpected token Object(123).toString() // "123"// 改造后調(diào)用 toStr toStr([1, 2, 3]) // "[object Array]" toStr('123') // "[object String]" toStr(123) // "[object Number]" toStr(Object(123)) // "[object Number]"

上面例子首先使用 Function.prototype.call 函數(shù)指定一個(gè) this 值,然后 .bind 返回一個(gè)新的函數(shù),始終將 Object.prototype.toString 設(shè)置為傳入?yún)?shù),其實(shí)等價(jià)于 Object.prototype.toString.call()

實(shí)現(xiàn) Currying 函數(shù)

可以理解所謂的柯里化函數(shù),就是封裝==一系列的處理步驟==,通過閉包將參數(shù)集中起來計(jì)算,最后再把需要處理的參數(shù)傳進(jìn)去

實(shí)現(xiàn)原理就是用閉包把傳入?yún)?shù)保存起來,當(dāng)傳入?yún)?shù)的數(shù)量足夠執(zhí)行函數(shù)時(shí),就開始執(zhí)行函數(shù)

上面延遲計(jì)算部分已經(jīng)實(shí)現(xiàn)了一個(gè)簡化版的 Currying 函數(shù)

下面實(shí)現(xiàn)一個(gè)更加健壯的 Currying 函數(shù)

function currying(fn, length) {// 第一次調(diào)用獲取函數(shù) fn 參數(shù)的長度,后續(xù)調(diào)用獲取 fn 剩余參數(shù)的長度length = length || fn.lengthreturn function (...args) { // 返回一個(gè)新函數(shù),接收參數(shù)為 ...args// 新函數(shù)接收的參數(shù)長度是否大于等于 fn 剩余參數(shù)需要接收的長度return args.length >= length? fn.apply(this, args) // 滿足要求,執(zhí)行 fn 函數(shù),傳入新函數(shù)的參數(shù): currying(fn.bind(this, ...args), length - args.length)// 不滿足要求,遞歸 currying 函數(shù)// 新的 fn 為 bind 返回的新函數(shù),新的 length 為 fn 剩余參數(shù)的長度} }// Test const fn = currying(function(a, b, c) {console.log([a, b, c]); })fn("a", "b", "c") // ["a", "b", "c"] fn("a", "b")("c") // ["a", "b", "c"] fn("a")("b")("c") // ["a", "b", "c"] fn("a")("b", "c") // ["a", "b", "c"]

上面使用的是 ES5 和 ES6 的混合語法

那如果不想使用 call/apply/bind 這些方法呢,自然是可以的,看下面的 ES6 極簡寫法,更加簡潔也更加易懂

const currying = fn =>judge = (...args) =>args.length >= fn.length? fn(...args): (...arg) => judge(...args, ...arg)// Test const fn = currying(function(a, b, c) {console.log([a, b, c]); })fn("a", "b", "c") // ["a", "b", "c"] fn("a", "b")("c") // ["a", "b", "c"] fn("a")("b")("c") // ["a", "b", "c"] fn("a")("b", "c") // ["a", "b", "c"]

如果還很難理解,看下面例子

function currying(fn, length) {length = length || fn.length; return function (...args) { return args.length >= length ? fn.apply(this, args) : currying(fn.bind(this, ...args), length - args.length) } }const add = currying(function(a, b, c) {console.log([a, b, c].reduce((a, b) => a + b)) })add(1, 2, 3) // 6 add(1, 2)(3) // 6 add(1)(2)(3) // 6 add(1)(2, 3) // 6

擴(kuò)展:函數(shù)參數(shù) length

函數(shù) currying 的實(shí)現(xiàn)中,使用了 fn.length 來表示函數(shù)參數(shù)的個(gè)數(shù),那 fn.length 表示函數(shù)的所有參數(shù)個(gè)數(shù)嗎?并不是

函數(shù)的 length 屬性獲取的是形參的個(gè)數(shù),但是形參的數(shù)量不包括剩余參數(shù)個(gè)數(shù),而且僅包括第一個(gè)具有默認(rèn)值之前的參數(shù)個(gè)數(shù),看下面的例子

((a, b, c) => {}).length; // 3((a, b, c = 3) => {}).length; // 2 ((a, b = 2, c) => {}).length; // 1 ((a = 1, b, c) => {}).length; // 0 ((...args) => {}).length; // 0const fn = (...args) => {console.log(args.length); } fn(1, 2, 3) // 3

所以在柯里化的場景中,不建議使用 ES6 的函數(shù)參數(shù)默認(rèn)值

const fn = currying((a = 1, b, c) => {console.log([a, b, c]) })fn() // [1, undefined, undefined]fn()(2)(3) // Uncaught TypeError: fn(...) is not a function

我們期望函數(shù) fn 輸出 1, 2, 3,但是實(shí)際上調(diào)用柯里化函數(shù)時(shí) ((a = 1, b, c) => {}).length === 0

所以調(diào)用 fn() 時(shí)就已經(jīng)執(zhí)行并輸出了 1, undefined, undefined,而不是理想中的返回閉包函數(shù)

所以后續(xù)調(diào)用 fn()(2)(3) 將會(huì)報(bào)錯(cuò)

小結(jié)&鏈接

定義:柯里化是一種將使用多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)

實(shí)際應(yīng)用

  • 延遲計(jì)算:部分求和、bind 函數(shù)
  • 動(dòng)態(tài)創(chuàng)建函數(shù):添加監(jiān)聽 addEvent、惰性函數(shù)
  • 參數(shù)復(fù)用:Function.prototype.call.bind(Object.prototype.toString)
  • 實(shí)現(xiàn) Currying 函數(shù):用閉包把傳入?yún)?shù)保存起來,當(dāng)傳入?yún)?shù)的數(shù)量足夠執(zhí)行函數(shù)時(shí),就開始執(zhí)行函數(shù)

    函數(shù)參數(shù) length:獲取的是形參的個(gè)數(shù),但是形參的數(shù)量不包括剩余參數(shù)個(gè)數(shù),而且僅包括==第一個(gè)參數(shù)有默認(rèn)值之前的參數(shù)個(gè)數(shù)==

    參考文章:JavaScript專題之函數(shù)柯里化

    博客地址:https://ainyi.com/74

    轉(zhuǎn)載于:https://www.cnblogs.com/ainyi/p/10918175.html

    總結(jié)

    以上是生活随笔為你收集整理的js 高阶函数之柯里化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。