惠州定制网站制作推荐,网络物流平台,自己的免费网站空间,西安seo代理计费博客地址#xff1a;https://ainyi.com/74 定义 在计算机科学中#xff0c;柯里化#xff08;Currying#xff09;是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数#xff0c;并且返回接受余下的参数且返回结果的新函数的技术 就是只传递给函数… 博客地址https://ainyi.com/74 定义 在计算机科学中柯里化Currying是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数并且返回接受余下的参数且返回结果的新函数的技术 就是只传递给函数某一部分参数来调用返回一个新函数去处理剩下的参数闭包 常用的封装成 add 函数 // reduce 方法
const add (...args) args.reduce((a, b) a b)// 传入多个参数执行 add 函数
add(1, 2) // 3// 假设有一个 currying 函数
let sum currying(params)
sum(1)(3) // 4 实际应用 延迟计算 部分求和例子说明了延迟计算的特点 const add (...args) args.reduce((a, b) a b)// 简化写法
function currying(func) {const args []return function result(...rest) {if (rest.length 0) {return func(...args)} else {args.push(...rest)return result}}
}const sum currying(add)sum(1, 2)(3) // 未真正求值收集参数的和
sum(4) // 未真正求值收集参数的和
sum() // 输出 10 上面的代码理解先定义 add 函数然后 currying 函数就是用闭包把传入参数保存起来当传入参数的数量足够执行函数时就开始执行函数 上面的 currying 函数是一种简化写法判断传入的参数长度是否为 0若为 0 执行函数否则收集参数到 args 数组 另一种常见的应用是 bind 函数我们看下 bind 的使用 let obj {name: Krry
}
const fun function () {console.log(this.name)
}.bind(obj)fun() // Krry 这里 bind 用来改变函数执行时候的上下文this但是函数本身并不执行所以本质上是延迟计算这一点和 call / apply 直接执行有所不同 动态创建函数 有一种典型的应用情景是这样的每次调用函数都需要进行一次判断但其实第一次判断计算之后后续调用并不需要再次判断这种情况下就非常适合使用柯里化方案来处理 即第一次判断之后动态创建一个新函数用于处理后续传入的参数并返回这个新函数。当然也可以使用惰性函数来处理本例最后一个方案会介绍 我们看下面的这个例子在 DOM 中添加事件时需要兼容现代浏览器和 IE 浏览器IE 9方法就是对浏览器环境进行判断看浏览器是否支持简化写法如下 // 简化写法
function addEvent (type, el, fn, capture false) {if (window.addEventListener) {el.addEventListener(type, fn, capture);}else if(window.attachEvent) {el.attachEvent(on type, fn);}
} 但是这种写法有一个问题就是每次添加事件都会调用做一次判断比较麻烦 可以利用闭包和立即调用函数表达式IIFE来实现只判断一次后续都无需判断 const addEvent (function(){if (window.addEventListener) {return function (type, el, fn, capture) { // 关键el.addEventListener(type, fn, capture)}}else if(window.attachEvent) {return function (type, el, fn) { // 关键el.attachEvent(on type, fn)}}
})() 上面这种实现方案就是一种典型的柯里化应用在第一次的 if...else if... 判断之后完成第一次计算然后动态创建返回新的函数用于处理后续传入的参数 这样做的好处就是之后调用之后就不需要再次调用计算了 当然可以使用惰性函数来实现这一功能原理很简单就是重写函数 function addEvent (type, el, fn, capture false) {// 重写函数if (window.addEventListener) {addEvent function (type, el, fn, capture) {el.addEventListener(type, fn, capture);}}else if(window.attachEvent) {addEvent function (type, el, fn) {el.attachEvent(on type, fn);}}// 执行函数有循环爆栈风险addEvent(type, el, fn, capture);
} 第一次调用 addEvent 函数后会进行一次环境判断在这之后 addEvent 函数被重写所以下次调用时就不会再次判断环境 参数复用 我们知道调用 toString() 可以获取每个对象的类型但是不同对象的 toString() 有不同的实现 所以需要通过 Object.prototype.toString() 来获取 Object 上的实现 同时以 call() / apply() 的形式来调用并传递要检查的对象作为第一个参数 例如下面这个例子 function isArray(obj) { return Object.prototype.toString.call(obj) [object Array];
}function isNumber(obj) {return Object.prototype.toString.call(obj) [object Number];
}function isString(obj) {return Object.prototype.toString.call(obj) [object String];
}// Test
isArray([1, 2, 3]) // true
isNumber(123) // true
isString(123) // true 但是上面方案有一个问题那就是每种类型都需要定义一个方法这里我们可以使用 bind 来扩展优点是可以直接使用改造后的 toStr const toStr Function.prototype.call.bind(Object.prototype.toString);// 改造前直接调用
[1, 2, 3].toString() // 1,2,3
123.toString() // 123
123.toString() // SyntaxError: Invalid or unexpected token
Object(123).toString() // 123// 改造后调用 toStr
toStr([1, 2, 3]) // [object Array]
toStr(123) // [object String]
toStr(123) // [object Number]
toStr(Object(123)) // [object Number] 上面例子首先使用 Function.prototype.call 函数指定一个 this 值然后 .bind 返回一个新的函数始终将 Object.prototype.toString 设置为传入参数其实等价于 Object.prototype.toString.call() 实现 Currying 函数 可以理解所谓的柯里化函数就是封装一系列的处理步骤通过闭包将参数集中起来计算最后再把需要处理的参数传进去 实现原理就是用闭包把传入参数保存起来当传入参数的数量足够执行函数时就开始执行函数 上面延迟计算部分已经实现了一个简化版的 Currying 函数 下面实现一个更加健壮的 Currying 函数 function currying(fn, length) {// 第一次调用获取函数 fn 参数的长度后续调用获取 fn 剩余参数的长度length length || fn.lengthreturn function (...args) { // 返回一个新函数接收参数为 ...args// 新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度return args.length length? fn.apply(this, args) // 满足要求执行 fn 函数传入新函数的参数: currying(fn.bind(this, ...args), length - args.length)// 不满足要求递归 currying 函数// 新的 fn 为 bind 返回的新函数新的 length 为 fn 剩余参数的长度}
}// Test
const fn currying(function(a, b, c) {console.log([a, b, c]);
})fn(a, b, c) // [a, b, c]
fn(a, b)(c) // [a, b, c]
fn(a)(b)(c) // [a, b, c]
fn(a)(b, c) // [a, b, c] 上面使用的是 ES5 和 ES6 的混合语法 那如果不想使用 call/apply/bind 这些方法呢自然是可以的看下面的 ES6 极简写法更加简洁也更加易懂 const currying fn judge (...args) args.length fn.length? fn(...args): (...arg) judge(...args, ...arg)// Test
const fn currying(function(a, b, c) {console.log([a, b, c]);
})fn(a, b, c) // [a, b, c]
fn(a, b)(c) // [a, b, c]
fn(a)(b)(c) // [a, b, c]
fn(a)(b, c) // [a, b, c] 如果还很难理解看下面例子 function currying(fn, length) {length length || fn.length; return function (...args) { return args.length length ? fn.apply(this, args) : currying(fn.bind(this, ...args), length - args.length) }
}const add currying(function(a, b, c) {console.log([a, b, c].reduce((a, b) a b))
})add(1, 2, 3) // 6
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2, 3) // 6 扩展函数参数 length 函数 currying 的实现中使用了 fn.length 来表示函数参数的个数那 fn.length 表示函数的所有参数个数吗并不是 函数的 length 属性获取的是形参的个数但是形参的数量不包括剩余参数个数而且仅包括第一个具有默认值之前的参数个数看下面的例子 ((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; // 0const fn (...args) {console.log(args.length);
}
fn(1, 2, 3) // 3 所以在柯里化的场景中不建议使用 ES6 的函数参数默认值 const fn currying((a 1, b, c) {console.log([a, b, c])
})fn() // [1, undefined, undefined]fn()(2)(3) // Uncaught TypeError: fn(...) is not a function 我们期望函数 fn 输出 1, 2, 3但是实际上调用柯里化函数时 ((a 1, b, c) {}).length 0 所以调用 fn() 时就已经执行并输出了 1, undefined, undefined而不是理想中的返回闭包函数 所以后续调用 fn()(2)(3) 将会报错 小结链接 定义柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数并且返回接受余下的参数而且返回结果的新函数的技术 实际应用 延迟计算部分求和、bind 函数动态创建函数添加监听 addEvent、惰性函数参数复用Function.prototype.call.bind(Object.prototype.toString)实现 Currying 函数用闭包把传入参数保存起来当传入参数的数量足够执行函数时就开始执行函数 函数参数 length获取的是形参的个数但是形参的数量不包括剩余参数个数而且仅包括第一个参数有默认值之前的参数个数 参考文章JavaScript专题之函数柯里化 博客地址https://ainyi.com/74 转载于:https://www.cnblogs.com/ainyi/p/10918175.html