Comments (140)
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (arg) => judge(...args, arg)
from blog.
好难理解啊
from blog.
深受启发,这样理解柯里化 :用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数,有没有毛病
from blog.
@fi3ework 之所以写成 this 是因为希望根据环境的不同而设置不同的 this 值,我写了一个示例:
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function() {
var _args = args.slice(0),
arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}
}
var fn = curry(function(a, b, c) {
console.log(this)
});
var obj = {
value: 1,
fn: fn
}
obj.fn(1, 2, 3);
这个时候的 this 的值为 obj 对象,如果设置成 null ,就会变成打印 window 对象
from blog.
终于理解这句话了,“用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数”
from blog.
我现在才知道 function 的 length 竟然就是参数的长度,太水了 😄
很喜欢你的第二版,逻辑很清晰,很赞
from blog.
经历了这么多次面试这种定长 curry 问题我好像一次都没遇到过. 反倒是不定长 curry 的考察频频出现:
如实现这样一个 add: (头条一面, 腾讯一面考了同一题)
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 5
这样的题目其实是有问题的, 因为确实没有办法预先获取这个调用链的长度. 面试官应该传达一点, 其实给的注释并不代表 return 值...
因此, 相应的实现有:
const add = sum => {
const fn = n => add(n + sum);
fn.valueOf = () => sum;
return fn;
}
/** Test **/
add(1); // Function
+add(1); // 1
+add(1)(2); // 3
+add(1)(2)(3); // 5
按照犀牛书的定义, 这个可能不属于 curry , 就只是纯粹的链式调用罢了. 尽管有些混淆, 还是跟大家分享一下.
from blog.
没有说道 什么场景下使用 柯里化
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (arg) => judge(...args, arg)
参数不满足长度要求时,返回的函数仍需使用rest parameter:(...arg) => judge(...args, ...arg)
,否则除了第一次可以传递多个参数,后面只能一个一个传到足够为止了,不能直接处理fn(1,2)(3,4,5)的情况。
from blog.
函数的length
属性获取形参的个数,但是形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数。
((a, b, c = 3) => {}).length; // 2
((a, b = 2, c) => {}).length; // 1
((a = 1, b, c) => {}).length; // 0
((...args) => {}).length; // 0
如果使用了ES6的函数参数默认值,可能不适用柯里化的场景。
const fn = curry((a = 1, b, c) => {
console.log([a, b, c]);
});
fn()(2)(3); // Uncaught TypeError: fn(...) is not a function
在这种情况下,期望输出的是[1, 2, 3]
,而实际上fn()
已经输出了[1, undefined, undefined]
,而不是返回闭包函数。所以fn()(2)
将会报错。
from blog.
柯里化 是真的绕啊
from blog.
高颜值写法的 judge = (...args)
用的好巧妙
from blog.
var curry = (fn, ...args) =>
fn.length <= args.length
? fn(...args)
: curry.bind(null, fn, ...args)
var fn = curry(function(a, b, c){
console.log([a,b,c])
})
fn(1,2,3)
fn(1,2)(3)
fn(1)(2,3)
fn(1)(2)(3)
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 1, 2, 3 ]
参考_30s.js
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
这里依赖了fn.length
但是fn.length的取值逻辑可能是不与预期相符合的,会导致无效
const add = (a=1,b=1) => a + b;
console.log(add.length); // 预期是2,实际是0
from blog.
@iiicon 这算是个很细的知识啦,我也是很晚才知道的~
from blog.
function curry(length, fn) {
if (typeof length === 'function')
return curry.apply(this, Array.prototype.concat.apply([length.length], arguments));
var _args = arguments;
return function () {
var args = Array.prototype.slice.call(_args);
var i, next = 0, placeholderp = false;
while (++next < args.length && args[next] != curry._);
for (i = 0; i < arguments.length; i++) {
args[next] = arguments[i];
placeholderp |= args[next] === curry._;
while (++next < args.length && args[next] != curry._);
}
if (placeholderp || next < 2 + length || next < args.length)
return curry.apply(this, args);
else
return fn.apply(this, args.slice(2));
};
}
curry._ = {};
var _ = curry._;
var fn = curry(function (a, b, c, d, e) {
console.log([a, b, c, d, e]);
}, _, _, 3, 4, _);
fn(1, 2)(5);
fn(1, _)(2, _)(5);
// [1, 2, 3, 4, 5]
var arity3 = curry(3, function () {
console.log(arguments);
});
var arity2 = arity3(1);
var arity1 = arity3(1, 5) || arity2(5);
arity2(1, 2);
// [1, 1, 2];
arity1(2);
// [1, 5, 2]
var square = curry(Math.pow)(_, 2);
square(3);
// 9
var cubic = curry(Math.pow, _, 3);
cubic(2);
// 8
from blog.
//函数柯理化 其本质是降低通用性,提高适用性,其实是对闭包的极致应用
//原理:利用闭包保存一部分参数,返回一个包含了一部分参数的闭包。
//适用场景: ...
function connect(a, b, c, d,e,f,g) {
console.log(`${a}-${b}-${c}-${d}-${e}-${f}-${g}`);
}
//在闭包中,A闭包 由 B函数生成
//使用闭包可以引用函数的变量对象这一性质
//把闭包中存的变量和闭包接受的实参组合起来,传入目标函数
//简易版
function simpleCurry(fn){
let args = [].slice.call(arguments, 1);
return function () {
fn.apply(this, args.concat([].slice.call(arguments)))
}
}
//只可以分成两步,如果要可以分成任意层,就得用递归了
// simpleCurry(connect, 1, 2,5,67,8,4)(3);
// simpleCurry(connect, 1)(2, 3,4,5,6, 28);
//完整版,接受N层闭包,由于层数不定,递归也必须用到
//递归的过程中不断的 聚集参数,直到参数达到目标函数需要的个数,就执行函数
//如何知道函数接受的理想参数个数 fn.length
function curry(fn, args){
let length = fn.length; //目标函数理想的参数个数
let allArgs = args || [];
return function () {
let _args = [].slice.apply(arguments);
let _allArgs = allArgs.concat(_args)
//未达到理想参数个数就继续聚集参数, 达到了参数的个数,就可以运行目标函数
if (_allArgs.length < length){
//聚集参数的过程
return curry.call(this, fn, _allArgs)
}
else{
fn.apply(this, _allArgs);
}
}
}
curry(connect)(2, 3, 4, 5)(6, 1)(2);
//如果不想按顺序传入,则可以先用占位符,后面再填入数据
//比如
/**
*
let fn = curry(function(a, b, c) {
console.log([a, b, c]);
});
fn("a", _, "c")("b") // ["a", "b", "c"]
*
*
*/
let _;
function curry2(fn, args){
let allArgs = args || [];
let length = fn.length;
return function () {
let _args = [].slice.call(arguments);
let _allArgs = [].slice.call(allArgs);
//在这里来调整参数的位置, 如果前面有占位符就向前补位
if (_args.indexOf(_) !== -1){
//有占位符 就直接concat
_allArgs = _allArgs.concat(_args);
}
else{
//没有占位符,说明这段参数可以向前补位
_allArgs.forEach((v, i) => {
if (v === _ && _args.length != 0) {
_allArgs.splice(i, 1, _args.shift());
}
})
//剩下的还是添加进参数里面
if (_args.length != 0){
_allArgs = _allArgs.concat(_args);
}
}
//是否达到理想参数个数 以及是否还含有空位
if (_allArgs.length < length || _allArgs.indexOf(_) !== -1){
//继续聚集参数
return curry2.call(this, fn, _allArgs);
}
else{
fn.apply(this, _allArgs);
}
}
}
curry2(connect)(2, _, 4, 5)(_, 1)(_)("占位1", "占位2")("占位3");
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
果然高颜值,这里稍稍有些手误:
console.log(fn(1)(2, 3)); // arg => judge(...args, arg)
改下就好了:
var curry = fn =>
(judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)这有个问题.
例如:const add = (x, y, z) => x + y + z; const test = curry(add); test(1)(2, 3) // 返回的是函数不是6。因为判断只积累了两个参数。应该改成这样吧:
const curry = (fn) => judge = (...args) => fn.length === args.length ? fn(...args) : (...nextArgs) => judge(...args, ...nextArgs);
我觉得用函数的length属性去判断是不可取的。
比如const add = (x, y = 0, z = 0) => x+y+z;
就无法达到想要的效果。
又比如函数(...rest) => rest[0] + rest[1] + rest[2];
都无法用所说的实现方式去柯里化
from blog.
用ES6的写法,可以借助bind实现
function curry (fn, ...args) {
return args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args)
}
from blog.
搞个面试背诵版本
const curry = (fn, ...curryArgs) => (...args) =>
args.length >= fn.length
? fn(...curryArgs, ...args)
: curry(fn, ...curryArgs, ...args);
from blog.
let _curry = (fn) => {
let len = fn.length
function c(...args) {
if(len === args.length) return fn(...args)
else return c.bind(null, ...args)
}
return c
}
from blog.
@BugHshine 是这篇写得不好,日后修订时会重写
from blog.
@flymie sub_curry返回的这个函数,在参数不全(此处是调用次数不足)的情况是肯定不会触发,因为sub_curry永远都是返回一个新的函数,而不会去执行那个函数。来完整还原一下流程(去除了if条件):
首先是fn1
curry(fn0)
// 相当于
function(){
// length = 4
return curry(sub_curry(fn0), --length)
}
然后是fn1()
(function() {
return curry(sub_curry(fn0), --length);
})()
// 相当于
curry(function(){
return fn0()
}, 3)
// 相当于
function() {
// length = 3
return curry(sub_curry(function(){
return fn0();
}), --length)
}
最后就是fn1()()了
(function() {
return curry(sub_curry(function(){
return fn0();
}), --length)
})()
// 相当于
curry(function() {
return (function(){
return fn0();
})()
}), 2)
// 相当于
function() {
// length = 2
return curry(sub_curry(function() {
return (function(){
return fn0();
})()
}), --length)
}
也就是说:只要你的调用次数没有到达规定限度,随着调用次数的增加,传给curry这个方法的函数层级会越来越深。直到调用次数够了,才会一口气将这个函数执行完,你想要的console.log(1)也才会触发
from blog.
版本二写成这样不知可不可行?
function add(a, b, c, d) {
return a + b + c + d;
}
function curry(fn, length) {
return function (...args) {
if (args.length < length) {
return curry(fn.bind(this, ...args), length - args.length )
} else {
return fn.apply(this, args)
}
}
}
var addCurry = curry(add, 4);
console.log(addCurry(1)(2, 3, 4))
console.log(addCurry(1, 2, 3, 4)) // 10
from blog.
rest
参数之后不能再有其他参数。
// 报错
function f(a, ...b, c) {
// ...
}
// 正确
function f(a, b, ...c) {
// ...
}
但调用函数时,...
是数组的扩展运算符 (spread) 。例如 fn(...[1, 2, 3, 4])
,它用来把一个数组转换为用逗号分隔的参数序列。
扩展运算符与正常的函数参数可以结合使用,例如 fn(0, ...[1, 2, 3, 4], 5)
from blog.
curry化要求被传入函数所有参数都被明确的定义,因此当使用部分参数调用时,他会返回一个新的函数,在真正调用之前等待外部提供其余的参数。可以简单的理解为,在所有参数被提供之前,挂起或延迟函数的执行
--------------------------------以上是来自《javascript函数式编程指南》一书.
希望能帮助大家理解。
from blog.
占位符,那个地方,我看蒙住了T_T。
对于这个 fn(_, 2)(_, _, 4)(1)(3)(5)
想象中我以为等价为 fn(1, 2, 3, 5, 4)
可结果是 fn(1, 2, 3, 4, 5)
我是这样想的,先让占位符和非占位参数长度达到fn.length,之后再传进来的非占位参数依次替换掉之前的占位符。我这样想是不是错了,小白很慌,求救T_T
@mqyqingfeng
from blog.
柯里化还是会丢this,仍然不能把crry得到的函数等同一个不curry的函数,下个回答
我附了一个修改版,解决了这个毛病。
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function() {
var _args = args.slice(0),
arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}
}
var fn = curry(function(a, b, c) {
console.log(this.value)
});
var obj = {
value: 1,
fn: fn
}
obj.fn(1,2)(3);
undefined
from blog.
我试着写了一下,不知道对不对。
实现文中更易懂的实现方式
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function() {
var _args = args.concat([].slice.call(arguments));
if (_args.length < length) {
return curry.call(this, fn, _args);
}
return fn.apply(this, _args);
};
}
实现第三版
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function() {
var newArgs = [].slice.call(arguments);
for (var i = 0, len = args.length; i < len; i++) {
if (args[i] === _) {
args.splice(i, 1, newArgs.shift());
}
if (newArgs.length === 0) break;
}
var _args = args.concat(newArgs);
var _filterArr = _args.filter(ele => ele !== _);
if (_filterArr.length < length) {
return curry.call(this, fn, _args);
}
return fn.apply(this, _args);
};
}
from blog.
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
这种写法有一些问题吧?this
的绑定问题以及judge
变量的污染。
// example
var judge = 'judge'
var name = 'window'
var obj = {
name: 'obj',
say: function(a, b, c, d){ return `${this.name}'s params: ${[a, b, c, d].join(',')}` }
}
console.log(typeof judge) // => "string"
var curriedSay = curry.call(obj, obj.say) // 无法修改`this`的指向
console.log(curriedSay('a', 'b')('c')('d')) // => "window's params: a,b,c,d"
console.log(typeof judge) // => "function"
from blog.
有点无法理解第二版里面为啥要传一个length,还有就是fn.length 代表什么
length || fn.length 前面传length是记录当前已经收到几个参数了(如果收够了就执行),后面 fn.length是第一层处理时记录下被柯里化的函数的参数总个数
from blog.
let _={};
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function() {
var _args = args.slice(0),arg, i;
_args.forEach((item,index,arr) =>{
if(item==_&&arguments.length>0){
arr[index]=[].shift.call(arguments)
}
});
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length||_args.includes(_)) {
return curry.call(this, fn, _args);
}
else {
return fn.apply(this, _args);
}
}
}
var fn = curry(function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
})
``` ```
这个最终版感觉比较简洁 @mqyqingfeng
from blog.
经历了这么多次面试这种定长 curry 问题我好像一次都没遇到过. 反倒是不定长 curry 的考察频频出现:
如实现这样一个 add: (头条一面, 腾讯一面考了同一题)
add(1); // 1 add(1)(2); // 3 add(1)(2)(3); // 5这样的题目其实是有问题的, 因为确实没有办法预先获取这个调用链的长度. 面试官应该传达一点, 其实给的注释并不代表 return 值...
因此, 相应的实现有:
const add = sum => { const fn = n => add(n + sum); fn.valueOf = () => sum; return fn; } /** Test **/ add(1); // Function +add(1); // 1 +add(1)(2); // 3 +add(1)(2)(3); // 5按照犀牛书的定义, 这个可能不属于 curry , 就只是纯粹的链式调用罢了. 尽管有些混淆, 还是跟大家分享一下.
这里不定参数的实现好赞呀.. 但是这里调用的时候只能传入单个参数,针对这种add(1)(2, 3)(4, 5, 6)
就没有办法啦。按照楼上的方法,实现了个多参数的add版本
const add = (...param: number[]): Function => {
const addSum = param.reduce((prev, next) => prev + next)
const fn = (...args: number[]) => {
const sum = args.reduce((prev, next) => prev + next)
return add(addSum + sum)
}
fn.valueOf = () => addSum
return fn
}
console.log(+add(1)(1, 1, 1)(1)) // 5
console.log(+add(1)(2)(3, 4)) // 10
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)这有个问题.
例如:const add = (x, y, z) => x + y + z; const test = curry(add); test(1)(2, 3) // 返回的是函数不是6。因为判断只积累了两个参数。应该改成这样吧:
const curry = (fn) => judge = (...args) => fn.length === args.length ? fn(...args) : (...nextArgs) => judge(...args, ...nextArgs);我觉得用函数的length属性去判断是不可取的。
比如const add = (x, y = 0, z = 0) => x+y+z;
就无法达到想要的效果。
又比如函数(...rest) => rest[0] + rest[1] + rest[2];
都无法用所说的实现方式去柯里化
确实有很多限制
from blog.
日常学习
from blog.
专题系列专不知不觉更新了这么多,我得好好抽个时间系统学习一下😂
from blog.
@jawil 哈哈~ 还有五篇就要完结了,可以等完结后一起看~
from blog.
var name = person.map(function (item) {
return item.name;
})
const fn = function (item) {
return item.name;
}
person.map(fn)
PS:这样也可以参数复用
from blog.
@yangchongduo 柯里化的应用更多在函数式编程中,可以看 JS 函数式编程指南,在 《JavaScript专题之函数组合》这篇文章中也有涉及部分。
from blog.
@yangchongduo 这样确实可以做到参数复用,其实跟
var name = person.map(prop('name'))
是一样的,不过如果我们要获得其他属性呢?比如 'age'、'friends' 等,如果使用 fn 的话,还需要写多个 fn,而如果使用 prop 函数,就可以直接使用 prop('age') prop('friend')
from blog.
function curry1(fn, args) {
var length = fn.length;
args = args || [];
return function (...arguments) {
args = [...args, ...arguments]
return args.length < length ? curry1.call(this, fn, args) : fn.apply(this, args);
}
}
from blog.
值得一看~
from blog.
@hlmjack 正是如此~ o( ̄▽ ̄)d
from blog.
在更易懂的写法里return curry.call(this, fn, _args);
,为什么要用this
呢,感觉用null
是不是也可以呢
from blog.
我有个问题想请教下。
function (a, b, c){} 的参数有三个,fn('a', 'b', 'c', 'd') 正常工作,但 fn('a', 'b', 'c', 'd')('a') 就出错了。我知道这是因为 function (a, b, c){} 的参数个数,而导致的错误。
如果我想 fn('a', 'b',...n个参数)('a') 这样该如何写一个curry函数呢。
from blog.
@bepromising fn('a', 'b', 'c', 'd')('a') 是因为 fn('a', 'b', 'c', 'd') 就已经执行了该函数,该函数如果没有再返回一个函数,就肯定报错,说这并不是一个函数。
至于 fn('a', 'b',...n个参数)('a'),我不清楚为什么还要再传个 'a' 呢?
from blog.
第三版的代码是不是有bug呢,我试了一个稍微长一点的例子:
var _ = {}
var ary = curry(function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {
return [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]
})
console.log(ary(1,_,_)(_)(5,6,7,8,9,10)(11,12,13,14)(15,_,_,18,_)(20,21,22,_)(24,25,26)(2,3,4)(16)(17,19)(23))
结果输出是不对的,node下:
[Function]
chrome下:
ƒ () {
var _args = args.slice(0),
_holes = holes.slice(0),
argsLen = args.length,
holesLen = holes.length,
a…
顺便我自己用es6写了一个稍微简洁一点的,对应原文第三版,可能参数多的情况下性能会有问题,但感觉一般情况下更好理解一些
// hole为自己的指定占位符
function curry(fn, hole) {
const __len = fn.length
let args = [],
return function h() {
// 先把参数放入args数组
args = [...args, ...Array.from(arguments)]
// 如果长度超过原有函数参数列表长度,表示有占位
let holeNum = args.length - __len
// 第一个占位符对应的肯定是__len位置的变量,循环将所有占位符替换
for (let i = 0; i < holeNum; i++) {
args[args.indexOf(hole)] = args[__len]
args.splice(__len, 1)
}
// 如果没有占位符且参数数目已经够了
if (args.length < __len || args.indexOf(hole) > -1) {
return h
} else {
return fn.apply(null, args)
}
}
}
经测试上面的例子可以输出正确的结果
from blog.
@izuomeng 这个是最终的版本吗?我这边有报错
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script>
// hole为自己的指定占位符
function curry(fn, hole) {
const __len = fn.length
let args = [];
return function h() {
// 先把参数放入args数组
args = [...args, ...Array.from(arguments)]
// 如果长度超过原有函数参数列表长度,表示有占位
let holeNum = args.length - __len
// 第一个占位符对应的肯定是__len位置的变量,循环将所有占位符替换
for (let i = 0; i < holeNum; i++) {
args[args.indexOf(hole)] = args[__len]
args.splice(__len, 1)
}
// 如果没有占位符且参数数目已经够了
if (args.length < __len || args.indexOf(hole) > -1) {
return h
} else {
return fn.apply(null, args)
}
}
}
var _ = {};
var fn = curry(function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
});
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5)
</script>
</body>
</html>
from blog.
少了一行,抱歉😄
// hole为传入的占位符
function curry(fn, hole) {
const __len = fn.length
let args = [];
return function h() {
// 先把参数放入args数组
args = [...args, ...Array.from(arguments)]
// 如果长度超过原有函数参数列表长度,表示有占位
let holeNum = args.length - __len
// 第一个占位符对应的肯定是__len位置的变量,循环将所有占位符替换
for (let i = 0; i < holeNum; i++) {
args[args.indexOf(hole)] = args[__len]
args.splice(__len, 1)
}
// 如果没有占位符且参数数目已经够了
if (args.length < __len || args.indexOf(hole) > -1) {
return h
} else {
fn.apply(null, args)
return args = []
}
}
}
from blog.
@izuomeng 这个占位的 curry 比你想的要复杂一点…… 因为对于 fn(1, _, 3)(_, 4)(2)(5);
这样的例子,结果应该是 [1, 2, 3, 4, 5],如果使用这种方法的话,结果会是 [1, 2, 3, 5, 4]
from blog.
哦哦,看了几组执行结果才发现这个占位是边执行边填补的,我把他理解成从超出参数列表列表长度之后才开始填补的了,谢谢指教🙏
from blog.
@izuomeng 但是你说的问题还是存在的,而且现在的写法也很繁琐,我也在想更加简洁易懂的写法~
from blog.
请教下博住,第三版的填占位符是怎样一个规律。。。。
from blog.
@Wiolem 好方法,感谢补充~ ☆´∀`☆
from blog.
对于博主第二版的极简版的解释有点不太理解。
当执行 fn1()() 时,函数返回:
curry(sub_curry(function(){ return fn0() })) // 相当于 curry(function(){ return (function(){ return fn0() })() })
那么
(function(){ return fn0() })()
这个函数立即执行了,那么fn0函数也就执行了。此时工作台就因该有‘1’出现了啊。
下面是我对于博主第二版的极简版的理解:如有错误还请指出。
var fn = curry(fn0)
当执行 fn() 时,函数返回:
curry(sub_curry(fn0)) // 相当于 curry(function(){ return fn0(); })
此时我只需要把
(function(){ return fn0(); }
看做传入函数(起个名字为fn1吧)
curry(fn1); //对比curry(fn0),似是故人来。
当执行 fn()() 时,函数返回:
curry(sub_curry(fn1)) // 相当于 curry(function(){ return fn1(); })
同样可以看做传入函数(起个名字为fn2)。
curry(fn2);
当执行 fn()()() 时,同样
curry(function(){ return fn2(); })
同样可以看做传入函数(起个名字为fn3)。
curry(fn3);
当执行 fn()()()() 时,此时已不满足判断条件 if (length > 1) ,于是
return fn();
此时的传入的参数fn为fn3,
fn3() 就是 : (function(){ return fn2(); })(); fn2 =function(){ return fn1(); }; fn1 =function(){ return fn0(); }; 此时工作台才会出现‘1’
from blog.
@flymie 不会执行呀
curry(sub_curry(function(){
return fn0()
}))
// 相当于
curry(function(){
return (function(){
return fn0()
})()
})
// 相当于
curry(function(){
return fn0()
})
这时候不会执行呀~
from blog.
@mqyqingfeng
可能是我的描述有问题。
感觉sub_curry(fn)应该加了一条输出比较好。
function sub_curry(fn){ return function(){ console.log(1); //加了这条输出语句 return fn() } }
那么执行 fn1()()时候,
curry(sub_curry(function(){ console.log(1); return fn0() })) // 相当于 curry(function(){ return (function(){ console.log(1); return fn0() })() }) // 相当于 curry(function(){ return fnx(); //应该是相当于fnx吧。原句的这里是fn0,感觉就像函数执行过一样。 })
from blog.
好文,可惜现在内力不足,还不是很能消化
from blog.
我现在才知道 function 的 length 竟然就是参数的长度,太水了
很喜欢你的第二版,逻辑很清晰,很赞
其实表示的是未设置默认值的参数数量
比如
function test (arg1, arg2, arg3 = 3 ){
}
这里test.length=2
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
最后 (arg) => judge(...args, arg)
, 修改为 (...arg) => judge(...args, ...arg)
,才进一步完善。
不然 fn("a")("b", "c")('d')
这种情况就会只接收了 a, b, d
三个参数而出现问题
from blog.
rest可以这样用麽,记忆中是must be last
from blog.
@inottn 嗯是的,看错了。
from blog.
我认为@大笑平老师的写法是非常赞的。
我在这里针对这个方法做了一个小的优化,
用于解决:curry4(1, 2)(3, 4)
const curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg)
from blog.
柯里化的同时可以传入参数
const currying = (fn, ...arg1) => (...arg2) => [...arg1, ...arg2].length >= fn.length ? fn.apply(null, [...arg1, ...arg2]) : currying(fn, ...arg1, ...arg2)
from blog.
我认为@大笑平老师的写法是非常赞的。
我在这里针对这个方法做了一个小的优化,
用于解决:curry4(1, 2)(3, 4)const curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (...arg) => judge(...args, ...arg)
这么写就不是柯里化了,柯里化一次传一个参数
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
实践了一下,这里最后一行的参数只能在接受单个。应该写成:
let curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...argMore) => judge(...args, ...argMore);
这样测试fn('a')('b','c')时才能跟之前一样
from blog.
大大佬 你好,第三版的科里化,有个缺陷, 这种执行 fn(1, _, _, 4)(2, 3, 5) 就不能返回结果
from blog.
最后一种参数不定的,自己用自己的想法写了一种。本文作者讲得比较难理解的那种,看了半天,真的难以理解。
from blog.
大大佬 你好,第三版的科里化,有个缺陷, 这种执行 fn(1, _, _, 4)(2, 3, 5) 就不能返回结果
第三版存在的问题是无法同时传不连续的两个参,如:fn(1, _, , 4)(, 3)(2, 5)
问题是出在else if (holesLen) 这里, fn执行到(2,5)这一步时,_holes是 [1],当arguments循环到5时,仍然替换2时候的位置。只要添加个holesLen-- 就可以了。
else {
holesLen--
_args.splice(_holes[index], 1, arg);
_holes.splice(index, 1)
}
from blog.
大佬。 柯里化 return curry.call(this, fn, newArgs) 这个this本来就是指向的window, 为什么不直接 return curry(fn, newArgs)。
from blog.
看了好几遍,,一步步的console ,,终于懂了,,感谢大佬
from blog.
至此,我们已经实现了一个强大的 curry 函数,可是这个 curry 函数符合柯里化的定义吗?柯里化可是将一个多参数的函数转换成多个单参数的函数,但是现在我们不仅可以传入一个参数,还可以一次传入两个参数,甚至更多参数……这看起来更像一个柯里化 (curry) 和偏函数 (partial application) 的综合应用,可是什么又是偏函数呢?下篇文章会讲到。
那么符合函数柯里化的定义啊吗???
from blog.
试着写了个
function hasEmptyCurry (fn, hole = '_', ...args) {
// 存储被柯里化函数的形参个数
var beCurryFnLen = fn.length;
return function (...curryArgs) {
var j = 0,
combinationArgs = [],
holes = false;
for (var i = 0, len = args.length; i < len; i++) {
if (args[i] === hole) {
if (curryArgs[j] === undefined || curryArgs[j] === hole) {
++j;
} else {
args[i] = curryArgs[j];
curryArgs.splice(j, 1);
}
}
}
combinationArgs = args.concat(curryArgs);
holes = combinationArgs.indexOf(hole) !== -1;
if (holes || combinationArgs.length < beCurryFnLen) {
// 如果实参个数小于形参个数,则继续柯里化
return hasEmptyCurry.call(this, fn, hole, ...combinationArgs);
} else {
// 如果实参个数等于形参个数,则直接返回fn的执行结果
return fn.apply(this, combinationArgs);
}
}
}
from blog.
很久之前写过一个函数柯里化 无限层级 参数个数任意
var currying = function (fu) {
var _arg = [];
return function cb() {
if (arguments.length === 0) {
return fu.apply(null, _arg)
}
Array.prototype.push.apply(_arg, [].slice.call(arguments))
return cb
}
}
var sum = function () {
var total = 0;
var argArray = Array.prototype.slice.call(arguments)
argArray.forEach(function (item) {
total += item
});
return total
}
var toUpperCase = function () {
var total = []
var argArray = Array.prototype.slice.call(arguments)
argArray.forEach(function (item) {
total.push(item.toUpperCase())
});
return total
}
var fun1 = currying(sum);
var fun2 = currying(toUpperCase);
fun1(1, 2, 3)(1, 2, 3)(1, 2, 3, 1, 2, 3)
fun2('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')('a', 'a', 'a')
console.log(fun1());
console.log(fun2());
from blog.
内力不够,看的真吃力...
from blog.
function curry(fn, args) {
var length = fn.length;
args = args || [];
var that = this;
return function() {
var _args = args.slice(0),
arg, i;
if(that !== window)that_to = that;
else that_to = this;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
_args.push(arg);
}
if (_args.length < length) {
return curry.call(that_to, fn, _args);
}
else {
return fn.apply(that_to, _args);
}
}
}
var fn = curry(function(a, b, c) {
console.log(this.value)
});
var obj = {
value: 1,
fn: fn
}
obj.fn(1,2)(3);
稍微改了一下,解决了丢this的毛病,不过这也只适用于非严格模式了
from blog.
简单点
`
function curry (func) {
return function () {
return arguments.length === func.length ? func(...arguments) : curry (func).bind(null, ...arguments)
}
}
`
from blog.
const add = (...arg) => arg.reduce((prev, curr) => curr += prev, 0);
const currying = (fn, ...arg1) => (...arg2) => ((arg2.length === 0) ? fn(...arg1) : currying.call(null, fn, ...arg1, ...arg2))
const curryAdd = currying(add);
curryAdd(1, 2)(3)(4)();
curryAdd(1, 2)(3, 4)();
curryAdd(1)(2)(3)(4)();
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
from blog.
简单点
`function curry (func) { return function () { return arguments.length === func.length ? func(...arguments) : curry (func).bind(null, ...arguments) } }`
参数有可能超过,不能等于
from blog.
好难啊 感觉自己得多看几遍了
from blog.
感觉像bind函数的实现
from blog.
有点无法理解第二版里面为啥要传一个length,还有就是fn.length 代表什么
from blog.
var combined = [fn].concat(slice.call(arguments));
这个slice是不是得写成[].slice,我这里报错
from blog.
第二版这样写是不是更简洁:
function curry(fn) {
let totalArgLength = fn.length;
let totalArguments = [];
const slice = Array.prototype.slice;
return function () {
totalArguments = [].concat(totalArguments, slice.call(arguments, 0))
let argLength = arguments.length;
if (argLength < totalArgLength) {
totalArgLength -= argLength;
return arguments.callee
} else {
return fn.apply(null, totalArguments)
}
}
}
from blog.
bind就是柯里化的实现啊
function a(a, b){
console.log(a, b);
}
var fn = a.bind(this, "a");
fn("b");
from blog.
var combined = [fn].concat(slice.call(arguments));
这个slice是不是得写成[].slice,我这里报错
你看漏了一行
var slice = Array.prototype.slice;
这个slice 其实是这个
from blog.
这个实现是有严重BUG的,举个例子:
const bugFn = (a, b, c = 1) => {};
柯里化这个函数就废了。此时bugFn的length是2,但是他是3个参数。所以不能够用这种方式柯里化有默认值的函数
from blog.
这么说, subcurry
和 高颜值版的 : (arg) => judge(...args, arg);
有异曲同工之妙
from blog.
segmentfault 的@大笑平 补充的高颜值写法:
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)
这有个问题.
例如:
const add = (x, y, z) => x + y + z;
const test = curry(add);
test(1)(2, 3)
// 返回的是函数不是6。因为判断只积累了两个参数。
应该改成这样吧:
const curry = (fn) =>
judge = (...args) =>
fn.length === args.length
? fn(...args)
: (...nextArgs) => judge(...args, ...nextArgs);
from blog.
var curry = fn => judge = (...args) => args.length === fn.length ? fn(...args) : (arg) => judge(...args, arg)这种写法有一些问题吧?
this
的绑定问题以及judge
变量的污染。// example var judge = 'judge' var name = 'window' var obj = { name: 'obj', say: function(a, b, c, d){ return `${this.name}'s params: ${[a, b, c, d].join(',')}` } } console.log(typeof judge) // => "string" var curriedSay = curry.call(obj, obj.say) // 无法修改`this`的指向 console.log(curriedSay('a', 'b')('c')('d')) // => "window's params: a,b,c,d" console.log(typeof judge) // => "function"
改成这样就行。至于你说的污染问题,不存在的。因为你开始你就要在父作用域去定义这个变量。
let judge;
const curry = function(fn) {return judge = (...args) => fn.length === args.length ? fn.call(this, ...args) : (...nextArg) => judge(...args, ...nextArg)};
from blog.
项目里自用的实现,几乎是纯函数式写的项目,所以不存在 this 的问题。
export const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
export const hasOwnProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
export const argPlaceholder = {
// compatible with ramda.js
'@@functional/placeholder': true,
isArgPlaceholder: true
}
export const isArgPlaceholder = placeholder => isObject(placeholder) && hasOwnProperty(placeholder, 'isArgPlaceholder') && placeholder.isArgPlaceholder
export const simpleCurry = (fn, ...args) => args.length >= fn.length ? fn(...args) : (...args2) => curry(fn, ...args, ...args2)
export const internalCurry = (fn, filled, ...args) => {
let innerArgs = filled || []
innerArgs = innerArgs.map(innerArg => {
return isArgPlaceholder(innerArg) ? (args.shift() || innerArg) : innerArg
})
innerArgs = innerArgs.concat(args)
const innerLen = innerArgs.slice(0, fn.length).reduce((len, innerArg) => isArgPlaceholder(innerArg) ? len : len + 1, 0)
if (innerLen >= fn.length) {
return fn(...innerArgs)
} else {
return (...args2) => internalCurry(fn, innerArgs, ...args2)
}
}
export const curry = (fn, ...args) => internalCurry(fn, [], ...args)
from blog.
献丑,来个不定长的,面试快乐版。。其实我感觉这。。不柯里,面试看到这种题,基本确定面试官不懂柯里化。
function add2() {
let argsSum = Array.from(arguments).reduce((prev, next) => prev + next)
let fn = function () {
return add2(Array.from(arguments).reduce((prev, next) => prev + next) + argsSum)
}
fn.valueOf = () => argsSum
return fn
}
console.log(+add2(1, 2)(1))
from blog.
搞个面试背诵版本
const curry = (fn, ...curryArgs) => (...args) => args.length >= fn.length ? fn(...curryArgs, ...args) : curry(fn, ...curryArgs, ...args);
大佬, 这里 args.length >= fn.length 长度判断 应该还要加上 curryArgs.length 起始参数的长度把。
const curry = (fn, ...curryArgs) => (...args) =>
(args.length + curryArgs.length) >= fn.length
? fn(...curryArgs, ...args)
: curry(fn, ...curryArgs, ...args);
function add(a, b, c, d) {
console.log(a + b + c + d);
}
const _curry = curry(add, 1, 2, 3);
_curry(4);
from blog.
第三版中好像有一个bug:
- 如果占位符被消耗完了还继续添加参数的话只会添加到第一个位置而不会新增,原因是
_args.splice(_holes[index], 1, arg);
中_holes[index]
是undefined。测试函数:fn(1, _)(2, 3, 4, 5)
。
稍稍改动了下:
// 第三版
function curry(fn, args, holes) {
length = fn.length;
args = args || [];
holes = holes || [];
return function() {
var _args = args.slice(0),
_holes = holes.slice(0),
argsLen = args.length,
holesLen = holes.length,
arg, i, index = 0;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
// 处理类似 fn(1, _, _, 4)(_, 3) 这种情况,index 需要指向 holes 正确的下标
if (arg === _ && holesLen) {
index++
if (index > holesLen) {
_args.push(arg);
_holes.push(argsLen - 1 + index - holesLen)
}
}
// 处理类似 fn(1)(_) 这种情况
else if (arg === _) {
_args.push(arg);
_holes.push(argsLen + i);
}
// 处理类似 fn(_, 2)(1) 这种情况
else if (holesLen) {
// fn(_, 2)(_, 3)
if (index >= holesLen) {
_args.push(arg);
}
// fn(_, 2)(1) 用参数 1 替换占位符
else {
// _args.splice(_holes[index], 1, arg);
// _holes.splice(index, 1)
// 改动在这里
// 判断是否还存在占位符
if (_holes.length) {
_args.splice(_holes[index], 1, arg);
_holes.splice(index, 1)
} else {
_args.push(arg);
}
}
}
else {
_args.push(arg);
}
}
if (_holes.length || _args.length < length) {
return curry.call(this, fn, _args, _holes);
}
else {
return fn.apply(this, _args);
}
}
}
var _ = {};
var fn = curry(function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
});
fn(1, _)(2, 3, 4, 5); // 改动前数组长度永远都是2
from blog.
占位符,那个地方,我看蒙住了T_T。
对于这个fn(_, 2)(_, _, 4)(1)(3)(5)
想象中我以为等价为fn(1, 2, 3, 5, 4)
可结果是fn(1, 2, 3, 4, 5)
我是这样想的,先让占位符和非占位参数长度达到fn.length,之后再传进来的非占位参数依次替换掉之前的占位符。我这样想是不是错了,小白很慌,求救T_T
@mqyqingfeng
占位符是边传边填补的,也就是说fn(_, 2)(_, _, 4)(1)(3)(5)
第二个函数里的占位符会填补第一个函数的占位符,我理解为占位符顺延。对于第二个函数的参数来说,第一个占位符表示填充第一个函数中的占位符,此时占位符填满了,第二个占位符会新增上去。然后参数1填充第一个占位符,参数3填充第二个占位符,此时占位符已经用完了,参数5添加到末尾。
数组分别是[_, 2]
、[_, 2, _, 4]
、[1, 2, _, 4]
、[1, 2, 3, 4]
、[1, 2, 3, 4, 5]
。
这里理解的关键是第二个占位符会顺延第一个占位符到下一个函数,而不是新增一个占位符。
from blog.
@lin2006yuo 06年的?
from blog.
当执行 fn1()() 时,函数为什么返回
curry(sub_curry(function(){ return fn0() }))
啊,不是很理解
from blog.
@EmiyaYang 这里打印的是 6 啊,为啥你写的是5?
from blog.
Related Issues (20)
- 冴羽答读者问:如何在工作中打造影响力,带动同事?
- 无
- 冴羽答读者问:如何学习更有计划性、提升更稳更快? HOT 4
- 冴羽答读者问:过程比结果重要吗? HOT 1
- 冴羽答读者问:冴羽,你为什么写起了鸡汤? HOT 3
- 聊聊 npm 的语义化版本(Semver) HOT 4
- How to create Backlinks 😤
- 思考题第2题
- 可以理解为原型是prototype,原型链是通过__proto__ 链接起来的吗
- React 之 createElement 源码解读 HOT 8
- React 之元素与组件的区别 HOT 1
- React 之 Refs 的使用和 forwardRef 的源码解读
- React 之 Context 的变迁与背后实现 HOT 1
- 第一段不报错啊,刚试过了,会打印1
- how can i HOT 1
- Hosting of Blog Issue
- 全局对象
- 为啥每次都要创建一个 Child函数来new 子类?现在不都是 const p1 = new Person(); const p2 = new Person()吗
- 文档内容中文件结构的错位 HOT 1
- 原型链继承
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.