你也許不知道的 JavaScript 高級(jí)函數(shù)
高階函數(shù)是對(duì)其他函數(shù)進(jìn)行操作的函數(shù),可以將它們作為參數(shù)或通過(guò)返回它們。簡(jiǎn)單來(lái)說(shuō),高階函數(shù)是一個(gè)函數(shù),它接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為輸出返回。
例如?Array.prototype.map,Array.prototype.filter,Array.prototype.reduce?都是一些高階函數(shù)。
本文源自我的掘金?https://juejin.im/post/5e96c3...
尾調(diào)用和尾遞歸
尾調(diào)用(Tail Call)是函數(shù)式編程的一個(gè)重要概念,本身非常簡(jiǎn)單,一句話就能說(shuō)清楚。就是指某個(gè)函數(shù)的最后一步是調(diào)用另一個(gè)函數(shù)。
function g(x) {
console.log(x)
}
function f(x) {
return g(x)
}
console.log(f(1))
//上面代碼中,函數(shù)f的最后一步是調(diào)用函數(shù)g,這就是尾調(diào)用。上面代碼中,函數(shù) f 的最后一步是調(diào)用函數(shù) g,這就是尾調(diào)用。尾調(diào)用不一定出現(xiàn)在函數(shù)尾部,只要是最后一步操作即可。
函數(shù)調(diào)用自身,稱為遞歸。如果尾調(diào)用自身,就稱為尾遞歸。遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r(shí)保存成千上百個(gè)調(diào)用幀,很容易發(fā)生棧溢出錯(cuò)誤。但是隊(duì)伍尾遞歸來(lái)說(shuō),由于只存在一個(gè)調(diào)用幀,所以永遠(yuǎn)不會(huì)發(fā)生棧溢出錯(cuò)誤。
function factorial(n) {
if (n === 1) {
return 1
}
return n * factorial(n - 1)
}上面代碼是一個(gè)階乘函數(shù),計(jì)算 n 的階乘,最多需要保存 n 個(gè)調(diào)用數(shù)據(jù),復(fù)雜度為 O(n),如果改寫(xiě)成尾調(diào)用,只保留一個(gè)調(diào)用記錄,復(fù)雜度為 O(1)。
function factor(n, total) {
if (n === 1) {
return total
}
return factor(n - 1, n * total)
}斐波拉切數(shù)列也是可以用于尾調(diào)用。
function Fibonacci(n) {
if (n <= 1) {
return 1
}
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
//尾遞歸
function Fibona(n, ac1 = 1, ac2 = 1) {
if (n <= 1) {
return ac2
}
return Fibona(n - 1, ac2, ac1 + ac2)
}柯理化函數(shù)
在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中,柯里化是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。所謂柯里化就是把具有較多參數(shù)的函數(shù)轉(zhuǎn)換成具有較少參數(shù)的函數(shù)的過(guò)程。?
舉個(gè)例子
//普通函數(shù)
function fn(a, b, c, d, e) {
console.log(a, b, c, d, e)
}
//生成的柯里化函數(shù)
let _fn = curry(fn)
_fn(1, 2, 3, 4, 5) // print: 1,2,3,4,5
_fn(1)(2)(3, 4, 5) // print: 1,2,3,4,5
_fn(1, 2)(3, 4)(5) // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5) // print: 1,2,3,4,5柯理化函數(shù)的實(shí)現(xiàn)
// 對(duì)求和函數(shù)做curry化
let f1 = curry(add, 1, 2, 3)
console.log('復(fù)雜版', f1()) // 6
// 對(duì)求和函數(shù)做curry化
let f2 = curry(add, 1, 2)
console.log('復(fù)雜版', f2(3)) // 6
// 對(duì)求和函數(shù)做curry化
let f3 = curry(add)
console.log('復(fù)雜版', f3(1, 2, 3)) // 6
// 復(fù)雜版curry函數(shù)可以多次調(diào)用,如下:
console.log('復(fù)雜版', f3(1)(2)(3)) // 6
console.log('復(fù)雜版', f3(1, 2)(3)) // 6
console.log('復(fù)雜版', f3(1)(2, 3)) // 6
// 復(fù)雜版(每次可傳入不定數(shù)量的參數(shù),當(dāng)所傳參數(shù)總數(shù)不少于函數(shù)的形參總數(shù)時(shí),才會(huì)執(zhí)行)
function curry(fn) {
// 閉包
// 緩存除函數(shù)fn之外的所有參數(shù)
let args = Array.prototype.slice.call(arguments, 1)
return function() {
// 連接已緩存的老的參數(shù)和新傳入的參數(shù)(即把每次傳入的參數(shù)全部先保存下來(lái),但是并不執(zhí)行)
let newArgs = args.concat(Array.from(arguments))
if (newArgs.length < fn.length) {
// 累積的參數(shù)總數(shù)少于fn形參總數(shù)
// 遞歸傳入fn和已累積的參數(shù)
return curry.call(this, fn, ...newArgs)
} else {
// 調(diào)用
return fn.apply(this, newArgs)
}
}
}柯里化的用途
柯里化實(shí)際是把簡(jiǎn)答的問(wèn)題復(fù)雜化了,但是復(fù)雜化的同時(shí),我們?cè)谑褂煤瘮?shù)時(shí)擁有了更加多的自由度。而這里對(duì)于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在??吕锘举|(zhì)上是降低通用性,提高適用性。來(lái)看一個(gè)例子:
我們工作中會(huì)遇到各種需要通過(guò)正則檢驗(yàn)的需求,比如校驗(yàn)電話號(hào)碼、校驗(yàn)郵箱、校驗(yàn)身份證號(hào)、校驗(yàn)密碼等, 這時(shí)我們會(huì)封裝一個(gè)通用函數(shù) checkByRegExp ,接收兩個(gè)參數(shù),校驗(yàn)的正則對(duì)象和待校驗(yàn)的字符串
function checkByRegExp(regExp, string) {
return regExp.text(string)
}
checkByRegExp(/^1\d{10}$/, '18642838455') // 校驗(yàn)電話號(hào)碼
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]') // 校驗(yàn)郵箱我們每次進(jìn)行校驗(yàn)的時(shí)候都需要輸入一串正則,再校驗(yàn)同一類型的數(shù)據(jù)時(shí),相同的正則我們需要寫(xiě)多次, 這就導(dǎo)致我們?cè)谑褂玫臅r(shí)候效率低下,并且由于 checkByRegExp 函數(shù)本身是一個(gè)工具函數(shù)并沒(méi)有任何意義。此時(shí),我們可以借助柯里化對(duì) checkByRegExp 函數(shù)進(jìn)行封裝,以簡(jiǎn)化代碼書(shū)寫(xiě),提高代碼可讀性。
//進(jìn)行柯里化
let _check = curry(checkByRegExp)
//生成工具函數(shù),驗(yàn)證電話號(hào)碼
let checkCellPhone = _check(/^1\d{10}$/)
//生成工具函數(shù),驗(yàn)證郵箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)
checkCellPhone('18642838455') // 校驗(yàn)電話號(hào)碼
checkCellPhone('13109840560') // 校驗(yàn)電話號(hào)碼
checkCellPhone('13204061212') // 校驗(yàn)電話號(hào)碼
checkEmail('[email protected]') // 校驗(yàn)郵箱
checkEmail('[email protected]') // 校驗(yàn)郵箱
checkEmail('[email protected]') // 校驗(yàn)郵箱柯里化函數(shù)參數(shù) length
函數(shù) currying 的實(shí)現(xiàn)中,使用了 fn.length 來(lái)表示函數(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
// 0
const fn = (...args) => {
console.log(args.length)
}
fn(1, 2, 3)
// 3compose 函數(shù)
compose 就是組合函數(shù),將子函數(shù)串聯(lián)起來(lái)執(zhí)行,一個(gè)函數(shù)的輸出結(jié)果是另一個(gè)函數(shù)的輸入?yún)?shù),一旦第一個(gè)函數(shù)開(kāi)始執(zhí)行,會(huì)像多米諾骨牌一樣推導(dǎo)執(zhí)行后續(xù)函數(shù)。
const greeting = name => `Hello ${name}`
const toUpper = str => str.toUpperCase()
toUpper(greeting('Onion')) // HELLO ONIONcompose 函數(shù)的特點(diǎn)
compose 接受函數(shù)作為參數(shù),從右向左執(zhí)行,返回類型函數(shù)
fn()全部參數(shù)傳給最右邊的函數(shù),得到結(jié)果后傳給倒數(shù)第二個(gè),依次傳遞
compose 的實(shí)現(xiàn)
var compose = function(...args) {
var len = args.length // args函數(shù)的個(gè)數(shù)
var count = len - 1
var result
return function func(...args1) {
// func函數(shù)的args1參數(shù)枚舉
result = args[count].call(this, args1)
if (count > 0) {
count--
return func.call(null, result) // result 上一個(gè)函數(shù)的返回結(jié)果
} else {
//回復(fù)count初始狀態(tài)
count = len - 1
return result
}
}
}舉個(gè)例子
var greeting = (name) => `Hello ${name}`
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack'))大家熟悉的 webpack 里面的 loader 執(zhí)行順序是從右到左,是因?yàn)閣ebpack 選擇的是 compose 方式,從右到左依次執(zhí)行 loader,每個(gè) loader 是一個(gè)函數(shù)。
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]如上,webpack 使用了 style-loader 和 css-loader,它是先用 css-loader 加載.css 文件,然后 style-loader 將內(nèi)部樣式注入到我們的 html 頁(yè)面。
webpack 里面的 compose 代碼如下:
const compose = (...fns) => {
return fns.reduce(
(prevFn, nextFn) => {
return value =>prevFn(nextFn(value))
},
value => value
)
}推薦文章
筆者的年終總結(jié)在這里,希望能帶給你一點(diǎn)啟發(fā)。
