欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解JavaScript柯里化的概念和原理

 更新時(shí)間:2023年06月16日 10:42:59   作者:墨淵君  
在JS編程中, 函數(shù)是一等公民, 具備了強(qiáng)大的靈活性和復(fù)用性,而柯里化作為一種高階技術(shù), 可以進(jìn)一步提升函數(shù)的復(fù)用性和靈活性,在本篇博客中, 我們將深入探討 JS 中柯里化的概念和原理, 并了解其在實(shí)際開發(fā)中的應(yīng)用場景,需要的朋友可以參考下

引言

JS 編程中, 函數(shù)是一等公民, 具備了強(qiáng)大的靈活性和復(fù)用性。而 柯里化 作為一種高階技術(shù), 可以進(jìn)一步提升函數(shù)的復(fù)用性和靈活性。通過柯里化, 可以大大簡化函數(shù)的調(diào)用方式, 并創(chuàng)建更加靈活和可復(fù)用的函數(shù)

在本篇博客中, 我們將深入探討 JS 中柯里化的概念和原理, 并了解其在實(shí)際開發(fā)中的應(yīng)用場景。通過學(xué)習(xí)柯里化, 您將能夠編寫出更加簡潔、可讀性更高且易于維護(hù)的代碼, 提升自己的 JS 編程技能

讓我們開始探索 JS 柯里化的奇妙世界吧!

一、什么是柯里化

柯里化 (Currying), 又稱 部分求值 (Partial Evaluation), 是函數(shù)編程的一種高級(jí)技巧, 通常只需要傳遞給函數(shù) 一部分參數(shù) 來調(diào)用它, 讓它返回一個(gè)新的函數(shù)去 處理剩下的參數(shù)

如下 ???? 代碼, sum 是一個(gè)常規(guī)求和函數(shù), sumCurry 則是使用 柯里化 思想編寫的一個(gè)求和函數(shù), 它們區(qū)別就很明顯了, sum 在執(zhí)行時(shí)需要一次性進(jìn)行傳參, 但是 sumCurry 則不是, 它可以進(jìn)行進(jìn)行 分批傳入?yún)?shù)

// 常規(guī)求和函數(shù)
const sum = (a, b) => {
  console.log(a + b)
}
sum(1, 2) // 3
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
  console.log(a + b)
}
sumCurry(1)(2) // 3

補(bǔ)充說明: 柯里化 是函數(shù)高級(jí)技巧, 允許分批次處理參數(shù), 很多文章更多討論的是如何將一個(gè)普通函數(shù)轉(zhuǎn)為 柯里化 函數(shù), 我們需要將這兩者區(qū)分開來, 柯里化 并不是指將一個(gè)函數(shù)從可調(diào)用的 f(a, b, c) 轉(zhuǎn)換為可調(diào)用的 f(a)(b)(c) 這個(gè)過程, 而是指轉(zhuǎn)換后的函數(shù)被稱為 柯里化

二、柯里化作用

柯里化 的優(yōu)勢(shì)在于它為函數(shù)提供了更高的 靈活性復(fù)用性, 我們可以先提供部分函數(shù)參數(shù), 并在后續(xù)調(diào)用中根據(jù)需要提供剩余的參數(shù), 這種靈活性使得代碼更具可讀性、可維護(hù)性, 并且能夠創(chuàng)建具有不同功能的相關(guān)函數(shù), 下面我來看幾個(gè)具體的例子, 感受下 柯里化 的魅力

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

如下代碼, sumCurry柯里化 函數(shù), 同時(shí)存在變量 age, 基于 age 我們調(diào)用了函數(shù) sumCurry 獲得到一個(gè)新的函數(shù) addAge, 在后面我們可以隨時(shí)調(diào)用該函數(shù), 基于最初給的的參數(shù) age 追加數(shù)值

// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
  console.log(a + b)
}
const age = 18
const addAge = sumCurry(age)
addAge(1) // 19
addAge(10) // 28
addAge(8) // 26
addAge(0) // 18

2.2 函數(shù)復(fù)用

下面我們有這么一個(gè)需求, 需要抽取對(duì)象數(shù)組中 name 屬性, 并返回一個(gè)數(shù)組, 可實(shí)現(xiàn)代碼如下:

const users = [
  { name: 'lh', age: 18 },
  { name: 'myj', age: 28 },
  { name: 'jl', age: 20 },
]
const names = users.map(v => v.name)
console.log(names) // ['lh', 'myj', 'jl']

上面 ???? 代碼通過 map 來抽取數(shù)組對(duì)象中的 name 的屬性, 接下來我們嘗試使用 柯里化 對(duì)代碼進(jìn)行簡單的優(yōu)化:

// 使用「柯里化」創(chuàng)建一個(gè)提供 map 使用的、可復(fù)用函數(shù) 
const prop = (key) => (obj) => obj[key]
const users = [
  { name: 'lh', age: 18 },
  { name: 'myj', age: 28 },
  { name: 'jl', age: 20 },
]
const names = users.map(prop('name'))
console.log(names) // ['lh', 'myj', 'jl']

上面代碼很簡單, 但卻是 柯里化 的一個(gè)實(shí)踐, 有了 prop 函數(shù), 我們就可以很方便的, 抽取對(duì)象數(shù)組中指定元素, 而且在代碼閱讀上也相對(duì)來說清晰很多, 比如在上面基礎(chǔ)之上我們想要獲取所有 age 只需要這么編寫即可:

const ages = users.map(prop('age'))

2.3 提前計(jì)算

如下代碼, 是常見的一種兼容性代碼的寫法, 代碼中通過判斷 addEventListener 以及 attachEvent 為現(xiàn)代瀏覽器或 IE 瀏覽器的事件添加方法

const addEvent = (el, type, fn, capture) => {
  if (window.addEventListener) {
    el.addEventListener(type, (e) => {
      fn.call(el, e);
    }, capture);
  } else if (window.attachEvent) {
    el.attachEvent("on" + type, (e) => {
      fn.call(el, e);
    });
  } 
};

上面 ???? 代碼寫法沒啥毛病, 中規(guī)中距, 唯一的毛病可能就是在每次執(zhí)行 addEvent 時(shí)都需要進(jìn)行一次判斷, 這里我們就可以通過 柯里化 改造下, 提前 對(duì)兼容性進(jìn)行判斷, 最后返回添加事件的一個(gè) 主體方法

// 柯里化, 提前計(jì)算, 返回主體方法
const useEvent = () => {
  if (window.addEventListener) {
    return (el, sType, fn, capture) => {
      el.addEventListener(sType, (e) => {
        fn.call(el, e);
      }, (capture));
    };
  } else if (window.attachEvent) {
    return (el, sType, fn, capture) => {
      el.attachEvent("on" + sType, (e) => {
        fn.call(el, e);
      });
    };
  }
}
const addEvent = useEvent()

2.4 延遲計(jì)算(運(yùn)行)

延遲計(jì)算其實(shí)也好理解, 就是每次函數(shù)執(zhí)行都會(huì) 收集一部分參數(shù), 直到所有參數(shù)都準(zhǔn)備完畢, 才會(huì)進(jìn)行計(jì)算, 如下代碼:

// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => (c) => (d) => {
  console.log(a + b + c + d)
}
sumCurry(1)(2)(3)(4) // 10

三、普通函數(shù)如果轉(zhuǎn)換為「柯里化」

接下來我們嘗試寫一個(gè)轉(zhuǎn)換函數(shù) curry, 通過它我們可以將普通函數(shù) f(a, b, c, d) 轉(zhuǎn)為柯里化函數(shù) f(a)(b)(c), 轉(zhuǎn)換后 柯里化 函數(shù)的 精髓 在于: 接收一部分參數(shù), 返回一個(gè)函數(shù)接收剩余參數(shù), 當(dāng)接收到足夠參數(shù)后, 執(zhí)行原函數(shù)

如下代碼所示, 我們實(shí)現(xiàn)了簡單的一個(gè)轉(zhuǎn)換函數(shù) curry, _curry 是個(gè)中轉(zhuǎn)函數(shù), 也是重點(diǎn):

  • _curry 本身就是個(gè) 柯里化 函數(shù), 它接收一個(gè) 原函數(shù)、以及 當(dāng)前已經(jīng)接收到的參數(shù)
  • _curry 返回一個(gè) 新的函數(shù), 用于 接收剩余的參數(shù)
  • 新函數(shù)接收若干參數(shù), 內(nèi)部會(huì)將之前收集的參數(shù)以及接收到的參數(shù)進(jìn)行合并, 并對(duì) 參數(shù)個(gè)數(shù)進(jìn)行判斷, 如果接收到足夠的參數(shù)了, 則執(zhí)行原函數(shù), 如果接收的參數(shù)不夠則執(zhí)行 _curry 函數(shù), 并返回一個(gè)新的函數(shù)繼續(xù)接收處理剩余的參數(shù)
/**
 * 中轉(zhuǎn)函數(shù)
 * @param fun           待柯里化的原函數(shù)
 * @param allArgs       已接收的參數(shù)列表
 * @returns {Function}  返回一個(gè)接收剩余參數(shù)的函數(shù)  
 */
const _curry = (fun, ...allArgs) => {
  // 1. 返回一個(gè)接收剩余參數(shù)的函數(shù)    
  return (...currentArgs) => {
    // 2. 當(dāng)前接收到的所有參數(shù)
    const _args = [...allArgs, ...currentArgs] 
    // 3. 接收到的參數(shù)大于或等于函數(shù)本身的參數(shù)時(shí), 執(zhí)行原函數(shù)
    if (_args.length >= fun.length) {
      return fun.call(this, ..._args)
    }
    // 4. 繼續(xù)執(zhí)行 _curry 返回一個(gè)接收剩余參數(shù)的函數(shù) 
    return _curry.call(this, fun, ..._args)
  }
}
/**
 * 將函數(shù)柯里化
 * @param fun  待柯里化的原函數(shù)
 * @returns {Function} 返回「柯里化」函數(shù)
 */
const curry = (fun) => _curry.call(this, fun)
// 測(cè)試
const sum = (a, b, c) => (a + b + c) // 原函數(shù)
const currySum = curry(sum) // 柯里化 函數(shù)
currySum(1)(2)(3) // 6
currySum(1)(2, 3) // 6
currySum(1, 2, 3) // 6

四、參數(shù)個(gè)數(shù)不定

下面我們回到 柯里化 本身, 還記得我們上文寫的 sumCurry 函數(shù)嗎? 該 柯里化 函數(shù)每次調(diào)用都只能傳一個(gè)參數(shù)

// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
  return a + b
}
sumCurry(1)(2) // 3

這里我們對(duì) sumCurry 進(jìn)行改造, 允許在每次調(diào)用時(shí)傳遞任意個(gè)數(shù)參數(shù)

// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (...preArgs) => (...nextArgs) => {
  return [...preArgs, ...nextArgs].reduce((total, ele) => (total + ele), 0)
}
sumCurry(1, 2, 3)() // 6
sumCurry(1, 2, 3)(4) // 10
sumCurry()(1, 4) // 5
sumCurry(1)(2) // 3

五、無限參數(shù)

上文我們所演示的 柯里化 函數(shù)允許被 連續(xù)調(diào)用次數(shù) 是固定的, 如果我們要實(shí)現(xiàn)一個(gè)可以 無限調(diào)用 的柯里化函數(shù)又該怎么處理呢? 實(shí)現(xiàn)原理其實(shí)也簡單, 重點(diǎn)就以下兩點(diǎn):

  • 只需要寫個(gè) 循環(huán)調(diào)用 即可, 函數(shù)每次返回新函數(shù), 新函數(shù)調(diào)用原函數(shù)并將上一次結(jié)果傳給原函數(shù)
  • 修改函數(shù) toString 方法, 用于取值
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry =  (...preArgs) => {
  // 1. 計(jì)算當(dāng)前結(jié)果
  const preTotal = preArgs.reduce((total, ele) => (total + ele), 0)
  // 2. 使用 bind, 基于原函數(shù)創(chuàng)建新函數(shù), 并將上一次結(jié)果作為第一個(gè)參數(shù)
  const _sumCurry = sumCurry.bind(this, preTotal)
  // 3. 修改 toString 返回值, 用于值值
  _sumCurry.toString = () => preTotal
  return _sumCurry
}
// 使用 + 強(qiáng)制調(diào)用 toString 方法
console.log(+sumCurry(1)) // 1
console.log(+sumCurry(1, 2, 3)) // 6
console.log(+sumCurry(1)(2)) // 3
console.log(+sumCurry(1)(2)(3)(4)(5)) // 15
console.log(+sumCurry(1)()(3)(4)(5)) // 13

六、柯里化和閉包的關(guān)系

閉包柯里化 是兩個(gè)不同的概念, 但它們之間有一定的關(guān)聯(lián):

  • 在編程中, 閉包允許函數(shù)捕獲并訪問其定義時(shí)的上下文中的變量, 即使在其定義環(huán)境之外被調(diào)用時(shí)也可以使用這些變量, 閉包可以通過函數(shù)返回函數(shù)的方式創(chuàng)建, 從而使得內(nèi)部函數(shù)可以訪問外部函數(shù)的變量

  • 柯里化是指高階函數(shù)使用技巧, 柯里化函數(shù)的特點(diǎn)是, 允許被連續(xù)調(diào)用 f(a)(b)(c) 每次調(diào)用傳遞若干參數(shù), 同時(shí)返回一個(gè) 接受剩余參數(shù)新函數(shù), 直到所有參數(shù)都被傳遞完畢, 才會(huì)執(zhí)行主體邏輯

  • 閉包和柯里化之間的關(guān)系在于, 柯里化函數(shù) 通常會(huì) 使用閉包實(shí)現(xiàn), 當(dāng)我們將一個(gè)函數(shù)進(jìn)行柯里化時(shí), 每次返回一個(gè)新的函數(shù), 這個(gè)新的函數(shù)會(huì)捕獲前一次調(diào)用時(shí)的參數(shù)和上下文, 這個(gè)上下文就形成了閉包, 使得新函數(shù)可以在后續(xù)調(diào)用中繼續(xù)訪問之前傳遞的參數(shù)和上下文

// 形成閉包, 返回的新函數(shù), 允許訪問父級(jí)函數(shù)的變量 a
const sumCurry = (a) => (b) => {
  console.log(a + b)
}

到此這篇關(guān)于深入理解JavaScript柯里化的概念和原理的文章就介紹到這了,更多相關(guān)JavaScript 柯里化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論