原文链接
高阶函数
高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入;
- 输出一个函数。
组合函数
- 组合函数就是将两个或两个以上的函数组合生成一个新函数的过程;
组合函数的作用
在项目开发过程中,为了实现函数的复用,我们通常会尽量保证函数的职责单一,在拥有单一功能函数的基础上,我们就可以自由的对函数进行组合,来实现特定的功能:
- 实现几个单一函数
function trim(input) {
return input && typeof input === 'string' ? input.trim() : input;
}
function lowerCase(input) {
return input && typeof input === "string" ? input.toLowerCase() : input;
}
function split(input, delimiter = ",") {
return typeof input === "string" ? input.split(delimiter) : input;
}
- 实现组合函数
function compose(...funcs) {
return function(x){
// 使用Array.prototype.reduce方法实现组合函数的调度,对应得执行顺序是从左到右
//如果你想从右往左执行的话,可以使用Array.prototype.reduceRight方法来实现
return funcs.reduce(function(arg, fn){
return fn(arg)
}, x)
}
}
- 调用组合函数
const trimLowercaseAndSplit = compose(trim, lowerCase, split);
let result = trimLowercaseAndSplit(" a,B,c")
console.log(result) // [ 'a', ' b', 'c' ]
柯里化(Currying)
- 柯里化可以把一个带有多个参数的函数转化为一系列的嵌套函数。
- 当柯里化后的函数接收到足够的参数后,就会开始执行原函数。而如果接收到的参数不足的话,就返回一个新的函数,用来接收余下新的函数。
- 在数学和理论计算机科学中的柯里化函数,一次只能传递一个参数,而对于JavaScript语言来说,在实际应用中的柯里化函数,可以传递一个或多个参数。
const add = function(a) {
return function(b) {
return a + b
}
}
const result = add(1)(2)
console.log(result)
柯里化的作用:
- 参数复用
- 实现一个多参数函数
function buildUri(scheme, domain, path){
return `${scheme}://${domain}/${path}`
}
- 调用lodash的curry函数,将多参数函数转化为嵌套函数
const _ = require("lodash")
let buildUriCurry = _.curry(buildUri)
- 调用转化后的嵌套函数实现参数复用
let commonUrl = buildUriCurry('http','localhost')
let indexUrl = commonUrl('index.html')
let listUrl = commonUrl('list.html')
- 延迟计算/运行
- 实现一个多参数函数
function add(a, b) {
return a + b;
}
- 调用lodash的curry函数,将多参数函数转化为嵌套函数
const _ = require("lodash")
const addCurry = _.curry(add)
- 传递第一个参数
const addNext = addCurry(1)
- 延迟传递第2个参数,实现延迟计算
setTimeout(function(){
let result = addNext(2)
console.log(result)
}, 1000)
柯里化的实现
// 1. 柯里化函数接收一个函数作为参数,这个参数即是原函数
// 2. 柯里化函数返回一个函数,即柯里化后的嵌套函数
function curry(func) {
// 3. 转化后的嵌套函数可以接收1个或多个参数
return function curried(...args) {
// 4. 判断传递的参数个数是否大于等于原函数的形参个数
// 通过func.length获取函数形参的个数
if(args.length >= func.length){
// 6. 传递的参数个数满额后,执行原函数
return func.apply(this, args)
}else{
return function(...args2){
// 5. 传递的参数个数不足原函数的形参个数,则返回一个函数继续接受参数,并将新的参数与之前的参数合并
// 递归调用,直至传递的参数个数大于等于原函数的形参个数
return curried.apply(this, args.concat(args2))
}
}
}
}
偏函数
- 元: 函数参数的个数
- 一元函数: 含有一个参数的函数
- 在计算机科学中,偏函数应用(Partrial Application)是指固定一个函数的某些参数,然后产生另外一个更小元的函数。
偏函数应用(Partial Application)很容易与函数柯里化混淆,它们之间的区别是:
- 偏函数应用是固定一个函数的一个或多个参数,并返回一个可以接收剩余参数的函数;
- 柯里化是将函数转化为可以嵌套的一元函数,也就是每个函数只接受一个参数。
实现一个偏函数
function partial(fn){
// 1. 获取除了第一个参数外的其他参数
var args = [].slice.call(arguments, 1)
console.log(args) //[ 'http', 'localhost' ]
// 2. 返回一个接收其它参数的函数
return function(){
// 3. 合并固定参数和新的参数
args = [].concat.apply(args, [].slice.apply(arguments))
console.log(args) //[ 'http', 'localhost', 'index.html' ]
// 4. 执行原函数, 并返回结果
return fn.apply(this, args) //http://localhost/index.html
}
}
var url = partial(buildUri, 'http', 'localhost')('index.html')
console.log(url)
惰性函数
由于不同的浏览器之间存在一些兼容性问题,这导致了我们在使用一些 Web API 时,需要解析判断,比如:
function addHandler(element, type, handler) {
if(element.addEventListener) {
element.addEventListener(type, handler, false)
} else if(element.attachEvent) {
element.attachEvent('on' + type, handler)
} else {
element["on" + type] = handler
}
}
在上面代码中,我们实现了不同浏览器 添加事件监听 的处理。代码实现起来也很简单,但存在一个问题,即每次调用的时候都需要进行判断,很明显这是不合理的。对于上述这个问题,我们可以通过惰性载入函数来解决。
所谓惰性载入即是当第1次根据条件执行函数后,在第2次调用函数时,就不再检查条件, 直接执行函数。
要实现这个功能,我们可以在第1次条件判断的时候,在满足判断条件的分支中覆盖掉所调用的函数,具体的实现方式如下所示:
function addHandler(element, type, handler) {
if(element.addEventListener){
addHandler = function(element, type, handler){
element.addEventListener(type, handler, false)
}
}else if (element.attachEvent){
addHandler = function(element, type, handler){
element.attachEvent("on" + type, handler)
}
}else{
addHandler = function(element, type, handler){
element["on" + type] = handler
}
}
return addHandler(element, type, handler)
}
除了使用以上的方式,我们也可以利用自执行函数来实现惰性载入:
通过自执行函数,在代码加载阶段就会执行一次条件判断,然后在对应得条件分支中返回一个新的函数,来实现对应得处理逻辑。
const addHandler = (function(){
if(document.addEventListener) {
return function(element, type, handler){
element.addEventListener(type, handler, false)
}
}else if (document.attachEvent){
return function(element, type, handler){
element.attachEvent('on' + type, handler)
}
}else{
return function(element, type, handler){
element["on" + type] = handler
}
}
})()
缓存函数
缓存函数是将函数的计算结果缓存起来,当下次以同样的参数调用该函数时,直接返回已缓存的结果,而无需再次执行函数。这是一种常见的以空间换时间的性能优化手段。
要实现缓存函数的功能,我们可以把经过序列化的参数作为key,在把第一次调用后的结果作为value存储到对象中。在每次执行函数调用前,都先判断缓存中是否存在有对应得key,如果有的话,直接返回该key对应得值。
function memorize(fn) {
const cache = Object.create(null) //存储缓存数据的对象
return function(...args) {
const _args = JSON.stringify(args)
console.log(cache)
return cache[_args] || (cache[_args] = fn.apply(fn, args))
}
}
let add = (a, b) => {
return a + b
}
const addCache = memorize(add)
console.log(addCache(1, 2))
console.log(addCache(1, 2))