[1, 2, 3].forEach(console.log)複製代碼
const splat = handle => (...array) => handle(array)
console.log(splat(array => array.reduce((a, b) => a * b))(1, 2, 3, 4))複製代碼
與面向對象方法將問題分解成多組"名詞"或對象不一樣,函數式方法將相同的問題分解成多組"動詞"或者函數。github
與面向對象相似的是,函數式編程也經過"粘結"或"組合"其餘函數的方式構建更大的函數,以實現更抽象的行爲。編程
函數式:經過把功能拆解成一個個小函數組件,再用函數講各個組件結合完成需求。json
面向對象:以對象爲抽象單元後端
function Team() {
this.persons = []
}
Team.prototype.push = function(person) {
this.persons.push(person)
}
Team.prototype.getAll = function() {
return this.persons
}
const team = new Team()
team.push('張三')
team.push('李四')
team.push('王五')
console.log(team.getAll())複製代碼
函數式:以函數爲抽象單元數組
const Team = () => {
const persons = []
return {
push: person => persons.push(person),
getAll: () => persons,
}
}
const team = Team()
team.push('張三')
team.push('李四')
team.push('王五')
console.log(team.getAll())複製代碼
字符串打印表格app
const csv = `name, age, hair
merble, , red
bob, 64, blonde`
const keys = Object.keys
const reduce = (obj, handler, initial = {}) => {
return keys(obj).reduce((last, key) => {
return handler(last, obj[key], key)
}, initial)
}
const chain = actions => (array) => {
return reduce(actions, (last, handle, action) => {
return last[action](handle)
}, array)
}
const split = (str, separator) => str.split(separator)
const trim = str => str.trim()
const trust = str => !!str.trim()
const csv2Table = str => split(str, '\n').map(row => chain({ filter: trust, map: trim })(split(row, ',')))
console.log(csv2Table(csv))複製代碼
用來判斷object與array是否非空ide
// 非空校驗
function existy(obj) {
return !!obj
}
console.log([null, undefined, false, '', [], {}].map(existy))
// 函數再加工,增長object與array非空校驗
function truthy(obj) {
return existy(obj) && (typeof obj === 'object' ? !!Object.keys(obj).length : true)
}
console.log([null, undefined, false, '', [], {}].map(truthy))複製代碼
若是參數是對象的屬性,屬性是個方法,則執行方法,屬性是個值,則直接返回值。若是不存在,返回undefined函數式編程
const existy = x => !!x
const execer = (condition, action) => (...args) => {
return existy(condition) ? action(...args) : undefined
}
const propExecer = (target, name) => (...args) => {
const action = target[name]
return execer(action, () => {
return typeof action === 'function' ? action.apply(target, args) : action
})()
}
[
propExecer([1, 2, 3], 'reverse')(),
propExecer({ foo: 42 }, 'foo')(),
propExecer([1, 2, 3], 'concat')([4], [5], [6]),
].map(console.log)複製代碼
這種例子比比皆是,好比根據後端的返回值,若是success是true,則執行刷新,若是是false,則提示用戶錯誤。函數
const existy = x => !!x
const execer = (condition, action) => (...args) => {
return existy(condition) ? action(...args) : undefined
}
const divider = actions => (...args) =>
actions.map(({ condition, action, name }) => ({ name, return: execer(condition, action)(...args) }))
/* 測試函數 */
const submit = (res) => {
const actions = {
waiting: (...args) => {
console.log(`waiting執行,參數:${args}`)
return '返回值: 成功'
},
success: (...args) => {
console.log(`success執行,參數:${args}`)
return '返回值: 成功'
},
fail: (...args) => {
console.log(`fail執行,參數:${args}`)
return '返回值: 失敗'
},
}
const mapper = isSuccess =>
[{
name: 'waiting',
condition: isSuccess === undefined,
action: actions.waiting,
}, {
name: 'success',
condition: isSuccess === true,
action: actions.success,
}, {
name: 'fail',
condition: isSuccess === false,
action: actions.fail,
}]
const { success, data } = res
return divider(mapper(success))(data)
}
console.log(submit({ success: undefined, data: '接口返回數據: 讀取中' }))
console.log(submit({ success: true, data: '接口返回數據: 讀取成功' }))
console.log(submit({ success: false, data: '接口返回數據: 讀取失敗' }))複製代碼
函數能夠去任何值能夠去的地方,不多有限制。好比數字在js中就是一等公民,函數同理。
函數和數字同樣能夠存儲爲變量
var fortytwo = function() { return 42 };
函數和數字同樣能夠存儲在數組的一個元素中
var fortytwos = [42, function() { return 42 }];
函數和數字同樣能夠做爲對象的成員變量
var fortytwos = {number: 42, fun: function() { return 42 } };
函數和數字同樣能夠在使用時直接建立
42 + (function() { return 42 })();
函數和數字同樣能夠傳遞給另外一個函數
function weirdAdd(n ,f) { return n + f() }
函數和數字同樣能夠被另外一個函數返回
function weirdAdd() { return function() { return 42 } }
重複唱一下內容直到數字爲1
直到x-1=1後,改成:牆上已經沒有啤酒了。
const _ = require('../util/understore')
// 牆上有x瓶啤酒
// x瓶啤酒
// 拿下一個來,分給你們
// 牆上還有x-1瓶啤酒
// x-1後,再循環唱一次。
// 直到x-1=1後,改成:牆上已經沒有啤酒了。
// 命令編程
for (let i = 99; i > 0; i--) {
if (i === 1) {
console.log('沒有啤酒了')
} else {
console.log(`牆上有${i}瓶啤酒,拿下一個來,分給你們。牆上還有${i - 1}瓶啤酒`)
}
}
// 函數式編程
const segment = i =>
_.chain([]).push(`牆上有${i}瓶啤酒,拿下一個來,分給你們,`).tap((data) => {
const remain = i - 1
if (remain > 0) {
data.push(`還剩${i - 1}瓶啤酒,`)
} else {
data.push('沒有啤酒了,')
}
}).push('你們喝吧\n')
.value()
const song = (start, end, seg) => _.range(start, end).reduce((arr, next) => arr.concat(seg(next)), [])
console.log(song(99, 1, segment).join(''))
// 元編程
function Point2D(x, y) {
this._x = x
this._y = y
}
function Ponit3D(x, y, z) {
Point2D.call(this, x, y)
this._z = z
}
console.log(new Ponit3D(1, 2, 3))複製代碼
reduce是函數式編程的核心之一,用途之廣隨處可見。
// 注意reduce與reduceRight的區別,一個從左計算,一個從右計算
const nums = [100, 2, 25]
const div = (x, y) => x / y
console.log(nums.reduce(div))
console.log(nums.reduceRight(div))
// 沒有明顯的順序關係,reduce 與 reduceRight相等, 以下reduce能夠換成reduceRight,結果相同
const all = (...args) => condition =>
args.reduce((truth, f) => (truth && f() === condition), true)
const any = (...args) => condition =>
args.reduce((truth, f) => (truth || f() === condition), false)
const T = () => true
const F = () => false
// 所有爲真
console.log(all(T, T)(true))
// 所有爲假
console.log(all(F, F)(false))
// 所有爲真,傳入所有假
console.log(all(F, F)(true))
// 所有爲假,傳入所有真
console.log(all(T, T)(false))
// 部分爲真
console.log(any(T, F)(true))
// 部分爲假
console.log(any(T, F)(false))
// 部分爲真,傳入所有假
console.log(any(F, F)(true))
// 部分爲假,傳入所有真
console.log(any(T, T)(false))複製代碼
使用reduce操做數組,進行排序,分組,統計數量等。
const people = [
{ name: 'Rick', age: 30, sex: 'man' },
{ name: 'Lucy', age: 24, sex: 'woman' },
{ name: 'Lily', age: 40, sex: 'woman' },
]
const sortBy = (datas, fn) =>
datas.sort((d1, d2) => fn(d1) - fn(d2))
console.log('sortBy', sortBy(people, p => p.age))
const groupBy = (datas, fn) =>
datas.reduce((last, data) =>
({ ...last, [`${fn(data)}`]: (last[fn(data)] || []).concat(data) }), {})
console.log('groupBy', groupBy(people, p => p.sex))
const countBy = (datas, fn) =>
datas.reduce((last, data) =>
({ ...last, [`${fn(data)}`]: (last[fn(data)] ? ++last[fn(data)] : 1) }), {})
console.log('countBy', countBy(people, p => p.sex))複製代碼
function cat(...rest) {
const [head, ...tail] = rest
return head.concat(...tail)
}
function mapcat(coll, fun) {
return cat(...coll.map(fun))
}
function removeLast(...coll) {
return coll.slice(0, -1)
}
function construct(head, ...tail) {
return cat([head], ...tail)
}
function interpose(coll, sep) {
return removeLast(...mapcat(coll, item => construct(item, sep)))
}
console.log(interpose([1, 2, 3], ','))複製代碼
一臉懵逼有木有。這就是函數式的精髓,把所有操做封裝成函數。仔細品味,思路清晰可見。
interpose鏈接最後結果,對外暴露很是簡單的API。removeLast用來移除數組最後一項。cat鏈接全部操做結果,合成一個數組。mapcat用來把每一項函數執行的結果傳遞給cat函數, 第二個參數函數,用來自定義操做函數如何拼接。固然,拼接也是個動做,咱們封裝在construct函數中。
邏輯思惟很差的人,請放棄函數式吧。哈哈哈。
最重要的環節來了,不囉嗦,直接上代碼
/* 對象操做 */
const keys = Object.keys
const identity = value => value
// 根據函數,把指定的對象轉成想要的任何格式,萬能函數
const reduce = (obj, handler, initial = {}) => keys(obj).reduce((last, key) => handler(last, obj[key], key), initial)
// 根據函數,把對象轉成想要的格式對象
const map = (obj, handler) => reduce(obj, (last, value, key) => (Object.assign(last, { [key]: handler(value, key) })))
// 根據函數,把對象轉成想要的格式數組
const map2Array = (obj, handler) => keys(obj).map((key, index) => handler(obj[key], key, index))
// 獲取json的值拼成數組,至關於Object.values
const values = obj => map2Array(obj, identity)
// 把{key:value} 轉成 [[key,value]] 的格式
const pairs = obj => map2Array(obj, (value, key) => ([key, value]))
// 反轉對象,交換key value
const invert = obj => reduce(obj, (last, value, key) => (Object.assign(last, { [value]: key })))
/* 數組操做 */
// 萃取json數組中的字段
const pluck = (datas, propertyName) => datas.map(data => data[propertyName])
// 把[[key,value]]格式數組轉json
const object = datas => datas.reduce((last, [key, value]) => (Object.assign(last, { [key]: value })), {})
/* 給json數組字段增長默認值 */
const defaults = (datas, misses) => datas.map((data) => {
const finalData = Object.assign({}, data)
map(misses, (value, key) => {
if (finalData[key] === undefined) {
finalData[key] = value
}
})
return finalData
})
/* 表查找 */
const findEqual = (datas, where) => {
const wheres = pairs(where)
return datas.filter(data => wheres.every(([key, value]) => data[key] === value))
}
/* 測試 */
// 模擬數據
const json = { file: 'day of the dead', name: 'bob' }
const array = [{ title: 't1', name: 'n1' }, { title: 't2', name: 'n2' }, { title: 't3' }]
// 對象
console.log(values(json))
console.log(pairs(json))
console.log(invert(json))
// 數組
console.log(pluck(array, 'title'))
console.log(defaults(array, { name: 'name' }))
console.log(object(pairs(json)))
console.log(object(pairs(json).map(([key, value]) => ([key.toUpperCase(), value]))))
console.log(findEqual(array, { title: 't3' }))複製代碼
最後是表格操做,徹底等同於SQL
/* 表格操做 */
const keys = Object.keys
const reduce = (obj, handler, initial = {}) => keys(obj).reduce((last, key) => handler(last, obj[key], key), initial)
const filter = (obj, handler) => reduce(obj, (last, value, key) => (handler(value, key) ? Object.assign(last, { [key]: value }) : last))
const pick = (obj, names) => filter(obj, (value, key) => names.includes(key))
const pluck = (datas, propertyName) => datas.map(data => data[propertyName])
/* 查找指定列數據 */
const findColumn = (datas, columns) => datas.map(data => pick(data, columns))
/* 返回對象新列名 */
const rename = (data, newNames) =>
reduce(newNames, (last, newName, oldName) => {
if (data[oldName] !== undefined) {
last[newName] = data[oldName]
delete last[oldName]
return last
}
return last
}, Object.assign({}, data))
/* 返回表格新列名 */
const asname = (table, newNames) => table.map(data => rename(data, newNames))
/* 根據where條件進行查找 */
const findWhere = (datas, handle) => {
return datas.filter(data => handle(data))
}
/* 測試 */
// 模擬數據
const table = [{ title: 't1', name: 'n1', age: 30 }, { title: 't2', name: 'n2', age: 40 }, { title: 't3', age: 50 }]
console.log(pluck(table, 'title'))
console.log(findColumn(table, ['title', 'name']))
console.log(rename({ title: 't1', name: 'n1' }, { title: 'tit' }))
console.log(asname(table, { title: 'tit' }))
console.log(findWhere(findColumn(asname(table, { title: 'tit' }), ['tit', 'age']), item => item.age > 40))複製代碼