本章的全部代碼,均在github.com/antgod/func…git
const max = (data) => {
return data.reduce((maxer, next) => {
return maxer > next ? maxer : next
})
}
console.log(max([5, 1, 3, 4, 2])) // 輸出5複製代碼
執行結果並無什麼奇怪,可是這樣特定的函數存在一個限制,若是咱們不是從數字而是從對象中尋找最大值,該怎麼辦?github
const max = (data, compare = item => item) => {
return data.reduce((maxer, next) => {
return compare(maxer) > compare(next) ? maxer : next
})
}
console.log(max([{ age: 64 }, { age: 32 }, { age: 50 }], item => item.age))複製代碼
可是,在某些方面,這個函數仍然受限,並非真正的函數式,讀者想一想看,爲何呢?算法
這個函數的大於號,是定死的。編程
咱們能夠構建一個新的函數,一個用來生成可比較的值,另外一個用來比較兩個值返回最佳值。設計模式
const finder = (data, need, compare) => {
return data.reduce((last, next) => {
return compare(last) === need(compare(last), compare(next)) ? last : next
})
}
const identity = prop => prop
console.log(finder([1, 2, 3, 4, 5], Math.max, identity))複製代碼
當咱們要比較對象大小時候,就能夠傳遞第三個參數了。數組
const finder = (data, need, compare) => {
return data.reduce((last, next) => {
return compare(last) === need(compare(last), compare(next)) ? last : next
})
}
const plucker = prop => item => item[prop]
console.log(finder([{ age: 64 }, { age: 32 }, { age: 50 }], Math.max, plucker('age')))複製代碼
當咱們要查找以B開頭的名字,怎麼辦呢?安全
const finder = (data, need, compare) => {
return data.reduce((last, next) => {
return compare(last) === need(compare(last), compare(next)) ? last : next
})
}
const plucker = prop => item => item[prop]
console.log(finder([{ name: 'A', age: 64 }, { name: 'B', age: 32 }, { name: 'C', age: 50 }], (x, y) => {
return x.charAt(0) === 'B' ? x : y
}, plucker('name')))複製代碼
咱們發現,函數雖然短小精幹,而且也按照了咱們的預期工做。可是卻有一些重複性代碼。閉包
// in finder
return compare(last) === need(compare(last), compare(next)) ? last : next
// in used
return x.charAt(0) === 'B' ? x : y複製代碼
你會發現,這兩段邏輯徹底相同,也就是說,這兩種算法都是返回最佳值或者當前值。dom
咱們徹底能夠按照如下思路縮減代碼:編程語言
const bester = (data, need) => {
return data.reduce((last, next) => {
return need(last, next) ? last : next
})
}
console.log(bester([{ name: 'A', age: 64 }, { name: 'B', age: 32 }, { name: 'C', age: 50 }], (x, y) => {
return x.age < y.age
}))複製代碼
上一章中,咱們建立了接受兩個函數參數的finder,而且簡化成一個函數參數的bester。事實上,在大多數js函數設計中,都不須要返回多個函數。
可是某些狀況下,咱們須要返回多個函數。讓咱們以repeat開始,介紹爲何要使用多個函數爲參數。
const range = (times) => {
const ranges = []
for (let idx = 0; idx < times; idx++) {
ranges.push(null)
}
return ranges
}
const repeat = (value, time) => {
return range(time).map(() => value)
}
console.log(repeat(4, 3))複製代碼
range函數對underscore的range作了下刪減,返回一個包含n個值爲null的數組。
做爲repeat的常規實現方式,咱們仍然有提升的空間。若是將重複值運算,那樣使用場景更普遍。
好比咱們要隨機生成10個10之內的數字(range函數與前文相同)。
const repeatness = (createValue, time) => {
return range(time).map((value, index) => createValue(index))
}
console.log(repeatness(() => Math.floor(Math.random() * 10) + 1, 10))複製代碼
有的時候,咱們不知道函數須要調用多少次,咱們只有一個條件。好比說,當咱們不斷重複調用一個函數,當函數超過某個閾值或者條件,咱們中止調用。使用repeatness明顯達不到咱們的需求。
好比,咱們想計算1024如下全部的2的整數指數值(2, 4, 8, 16, 32, 64, 128, 256, 512)。
const iterate = (createValue, checker, init) => {
const ret = []
let result = createValue(init)
while (checker(result)) {
ret.push(result)
result = createValue(result)
}
return ret
}
console.log(iterate(n => n + n, n => n < 1024, 1))複製代碼
函數接收兩個函數參數,一個用來執行動做,一個用來校驗結果。當結果知足時返回true。這算是repeatness的升級版了,連重複次數都是開放的,受到一個函數的執行結果影響。
回憶下以前repeatness返回三個常量的狀況
repeatness(() => 'Odelay', 3)複製代碼
這種返回常量的函數很是有用,是函數式編程的一種設計模式,經過函數來返回值。咱們常常稱之爲k,爲了清晰起見,咱們稱之爲alwasy。
const always = value => () => value
console.log(repeatness(always('Odelay'), 3))複製代碼
always的行爲能夠用來解釋閉包。閉包用來捕獲一個值,並屢次返回相同的值。每個新的閉包都會返回不同的值
const f = always(() => {})
console.log(f() === f())
// => true
const g = always(() => {})
console.log(g() === f())
// => false複製代碼
像always這樣的函數被稱爲組合子。
高階函數的參數用來配置返回函數的行爲。
注意觀察如下代碼:
const makeAdder = init => rest => init + rest
const add100 = makeAdder(100)
console.log(add100(38))
// => 138複製代碼
你會常常看到一個函數返回了一個捕獲變量的函數。
好比你要生成一個特定前綴的隨機字符串。
const uniqueString = prefix => [prefix, new Date().getTime()].join('')
console.log(uniqueString('ghosts'))
console.log(uniqueString('turkey'))複製代碼
看起來不錯,可是若是咱們須要自增的索引,而不是時間戳做爲後綴,怎麼辦?
可使用閉包來實現:
const generator = (init, prefix) => {
let counter = init
return (pre = prefix) => {
return [pre, counter++].join('')
}
}
const g1 = generator(0, 'prefix')
console.log(g1())
console.log(g1())
console.log(g1('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼
咱們也能夠用對象來實現:
const generator2 = (init, prefix) => {
return {
count: init,
uniqueString: function(pre = prefix) {
return [pre, this.count++].join('')
},
}
}
const g2 = generator2(0, 'prefix')
console.log(g2.uniqueString.call(g2))
console.log(g2.uniqueString.call(g2))
console.log(g2.uniqueString('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼
注意這裏使用了this訪問數據,那麼函數不能再使用箭頭函數。對象的缺點是不安全,由於能夠隨意訪問 count的值。不少時候隱藏實現細節是很重要的。事實上,咱們能夠把count像閉包同樣隱藏在函數內部。
const generator3 = (init, prefix) => {
let counter = init
return {
uniqueString: (pre = prefix) => {
return [pre, counter++].join('')
},
}
}
const g3 = generator3(0, 'prefix')
console.log(g3.uniqueString())
console.log(g3.uniqueString())
console.log(g3.uniqueString('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼
閉包的方式乾淨、簡單,可是也充滿了陷阱。
雖然對於外界操做來講,該變量是安全的。可是它會增長複雜度,當一個函數的返回值只依賴參數時,被稱爲引用透明。
這個詞看起來很花哨,意味着不破壞代碼結構的狀況下,用預期的值替換函數的任意調用。當你使用會改變內部代碼變量的閉包時,你不必定能作到這一點。由於不少閉包的返回值是依賴於調用次數的。也就是說,調用uniqueString10次與10000次,返回值是不一樣的。
因此咱們在任何地方調用閉包的函數時,都須要格外當心,有必要時,須要增長監控或者日誌。不然在閉包返回值值任意變化時,咱們每每找不到變化緣由。
咱們建立幾個高階函數的例子,第一個叫作fnull,咱們先來舉例說明下它的目的。
假設咱們有一組須要乘法的數組:
const nums = [1, 2, 3, null, 5]
console.log(nums.reduce((total, n) => (total * n)))複製代碼
很顯然null不會給咱們任何有用的答案。這時候fnull函數頗有用。
const nums = [1, 2, 3, null, 5]
const fillnull = (handle, ...args) => (...argvs) => handle(...argvs.map((argv, i) => argv || args[i]))
console.log(nums.reduce(fillnull((total, n) => { return total * n }, 1, 1)))複製代碼
函數檢查每一個傳入的參數是不是null或者undefined。若是是,則用默認值替換掉,而後再調用函數。
若是要查找的目標是對象,能夠用如下方式使用:
const defaults = d => (o, k) => {
const val = fillnull(identity, d[k])
return o && val(o[k])
}
const ages = [{ age: 100 }, { age: 120 }, { age: 150 }, { }, { age: 30 }]
const lookup = defaults({ age: 0 })
console.log(ages.reduce((total, age) => {
return total + lookup(age, 'age')
}, 0))複製代碼
其中defaults函數用來配置默認值,返回一個函數lookup,以後每次執行lookup函數,若是遇到空值,都會返回默認值代替。