I have not written articles for a long time due to my recent resignation and job hunting, so I had to put my articles written in 2017 out to cheat and like HHH.

concept

In my own words, function currying means that you can pass many arguments to the Curry function at once, or multiple times, and the curry function will return a function each time to process the remaining arguments until the final result is returned.

The instance

Here are some examples:

Curryized summation function

Var add1 = function(a, b, c){return a + b + c; Var add2 = function(a) {return function(b) {return function(c) {return a + b + c; }}}Copy the code

This returns a new function each time an argument is passed, and so on until the last time a+b+c is returned. But there is a problem with this implementation. There are only three parameters. What day does the product manager tell us that we need to change it 100 times? Let’s rewrite it 100 times, right? This clearly does not conform to the open closed principle, so we need to make a change to the function.

var add = function() { var _args = []; return function() { if(arguments.length === 0) { return _args.reduce(function(a, b) { return a + b; }) } [].push.apply(_args, arguments); return arguments.callee; } } var sum = add(); sum(100, 200)(300); sum(400); sum(); / / 1000Copy the code

We decide whether the function will run by judging whether to pass in the parameters next time. If we pass in the parameters, we will continue to save the parameters and run them all at once, so that we have preliminarily completed a curlized function.

General coriatization function

This is just a function that sums, but what if I multiply it? Do we have to rewrite it again? Take a closer look at our add function. If we replace the code inside if with a function execution code, does it become a generic function?

var curry = function(fn) { var _args = []; return function() { if(arguments.length === 0) { return fn.apply(fn, _args); } [].push.apply(_args, arguments); return arguments.callee; } } var multi = function() { return [].reduce.call(arguments, function(a, b) { return a + b; }) } var add = curry(multi); add(100, 200, 300)(400); add(1000); add(); / / 2000Copy the code

We have extended the previous method so that we have implemented a more general currie function. Now, you might ask, what if I don’t want to end with that ugly bracket every time?

var curry = function(fn) {
	var len = fn.length,
		args = [];
	return function() {
		Array.prototype.push.apply(args, arguments)
		var argsLen = args.length;
		if(argsLen < len) {
			return arguments.callee;
		}
		return fn.apply(fn, args);
	}
}
var add = function(a, b, c) {
	return a + b + c;
}

var adder = curry(add)
adder(1)(2)(3)
Copy the code

This is based on the number of arguments fn passes in, and it doesn’t return the final result of fn until the number of arguments fn needs is equal to the number of arguments fn needs. It’s the same principle as the previous method, but both methods rely too much on the number of arguments. I also saw another recursive implementation method in the brief book, in fact, the implementation of the train of thought and my similar.

Function createCurry(func, args) {var arity = func.length; var args = args || []; return function() { var _args = [].slice.call(arguments); [].push.apply(_args, args); If (_args.length < arity) {return createCurry. Call (this, func, _args); } // Return func. Apply (this, _args); }}Copy the code

This is a calculation of the number of parameters, what if I need an infinite number of parameters? Take the following scenario.

add(1)(2)(3)(2);
add(1, 2, 3, 4, 5);
Copy the code

The main point here is that the implicit conversion of functions involves two methods: toString and valueOf. If you evaluate the function directly, you will first convert the function to a string and then participate in the calculation. Using these two methods, you can modify the function.

var num = function() { } num.toString = num.valueOf = function() { return 10; } var anonymousNum = (function() { // 10 return num; } ())Copy the code

After modification, the final version of our function looks like this.

var curry = function(fn) {
	var func = function() {
		var _args = [].slice.call(arguments, 0);
		var func1 = function() {
			[].push.apply(_args, arguments)
			return func1;
		}
		func1.toString = func1.valueOf = function() {
			return fn.apply(fn, _args);
		}
		return func1;
	}
	return func;
}
var add = function() {
	return [].reduce.call(arguments, function(a, b) {
		return a + b;
	})
}

var adder = curry(add)
adder(1)(2)(3)
Copy the code

So, after all we’ve said, what’s the use of currie?

preload

In many cases, the parameters of the function that we need are likely to have some of the same parameters, so it would be wasteful to write them again. We load some parameters in advance and pass in the rest of the parameters. Here we mainly take advantage of the closure feature, through which the original scope can be kept.

var match = curry(function(what, str) {
  return str.match(what);
});

match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null
Copy the code

In the example above, the hasSpaces function is used to store regular expression rules, which effectively allows reuse of parameters.

Dynamically created function

This is also the idea of lazy functions, where we can execute the condition ahead of time and save it in a valid scope through closures, a common scenario for writing code.

var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); }};Copy the code

In this case, we will redo the if statement every time we call addEvent, but in reality the browser condition is unlikely to change. If you call addEvent once, you will get the same result as if you call it N times, so this will preload the condition.

var addEventHandler = function(){
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
}
var addEvent = addEventHandler();
addEvent(document.body, "click", function() {}, false);
addEvent(document.getElementById("test"), "click", function() {}, false);
Copy the code

There is a downside to this, though, because you can’t tell if this method is used in the application, but you still have to define addEvent at the top of the file, which is actually a waste of resources. Here’s a better solution.

var addEvent = function(el, sType, fn, capture){ if (window.addEventListener) { addEvent = function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { addEvent = function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; }}Copy the code

Reassign it in the addEvent function, which solves both the problem of judging it on every run and the waste of having to execute it at the top of the scope.

React

On the way home I was wondering if currying functions could be extended to more scenarios. I was thinking of replacing functions with react components. I thought of the higher-order components and redux’s Connect, which are really the embodiment of the currie idea applied to react. Let’s think about it. What if we replace the functions in the above example with components and the arguments with higher-order functions?

    var curry = function(fn) {
	var func = function() {
		var _args = [].slice.call(arguments, 0);
		var func1 = function() {
			[].push.apply(_args, arguments)
			return func1;
		}
		func1.toString = func1.valueOf = function() {
			return fn.apply(fn, _args);
		}
		return func1;
	}
	return func;
}

var hoc = function(WrappedComponent) {
	return function() {
		var len = arguments.length;
		var NewComponent = WrappedComponent;
		for (var i = 0; i < len; i++) {
			NewComponent = arguments[i](NewComponent)
		}
		return NewComponent;
	}
}
var MyComponent = hoc(PageList);
curry(MyComponent)(addStyle)(addLoading)
Copy the code

This example extends the original PageList component, adding style and loading to the PageList. If you want to add more functionality, you can continue the extension (note that addStyle and addLoading are both higher-order components), but the writing is really bad. Not coooooool at all, we can use the compose methods, underscore, and loadsh that are already provided in the library.

var enhance = compose(addLoading, addStyle);
enhance(MyComponent)
Copy the code

conclusion

In fact, the core of the use of Currie is the flexible use of function closures, a deep understanding of closures and scope can write a lot of flexible and clever methods.