亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

引言

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

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

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

一、什么是柯里化

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

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

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

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

二、柯里化作用

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

2.1 參數(shù)復用

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

// 使用「柯里化」思想編寫的一個求和函數(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ù)復用

下面我們有這么一個需求, 需要抽取對象數(shù)組中 name 屬性, 并返回一個數(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ù)組對象中的 name 的屬性, 接下來我們嘗試使用 柯里化 對代碼進行簡單的優(yōu)化:

// 使用「柯里化」創(chuàng)建一個提供 map 使用的、可復用函數(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']

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

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

2.3 提前計算

如下代碼, 是常見的一種兼容性代碼的寫法, 代碼中通過判斷 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 時都需要進行一次判斷, 這里我們就可以通過 柯里化 改造下, 提前 對兼容性進行判斷, 最后返回添加事件的一個 主體方法

// 柯里化, 提前計算, 返回主體方法
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 延遲計算(運行)

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

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

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

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

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

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

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

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

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

// 使用「柯里化」思想編寫的一個求和函數(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ù) 是固定的, 如果我們要實現(xiàn)一個可以 無限調(diào)用 的柯里化函數(shù)又該怎么處理呢? 實現(xiàn)原理其實也簡單, 重點就以下兩點:

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

六、柯里化和閉包的關系

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

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

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

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

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

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

相關文章

最新評論