2

I am reading through John Resig's Secrets of Javascript ninja and was trying out one of the examples on currying and parital functions. The code is as follows:

<html>
<body>
<button id="test">Click Me!</button>
</body>


<script type="text/javascript">
    Function.prototype.curry = function() {
        var fn = this,
        args = Array.prototype.slice.call(arguments);

       return function() {
           return fn.apply(this, args.concat(
               Array.prototype.slice.call(arguments)));
           }; 
       };


    var elem = document.getElementById("test"); 
    var bindClick = elem.addEventListener.curry("click");
    bindClick(function(){   console.log("OK"); });
</script>
</html>

However, the following code seems to generate an error Uncaught TypeError: Illegal invocation on the apply function.

I cant seem to figure out the reason as it all seems to make sense. bindClick will return an anonymous function that calls the function elem.addEventListener with window as the function context (this) and the arguments will be ["click", function() {console.log("OK"); }]

duplode
  • 33,731
  • 7
  • 79
  • 150
henrybai
  • 210
  • 3
  • 10
  • Actually, [this is not currying/schönfinkeling but partial application](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – Bergi Mar 22 '13 at 09:54
  • hmm. I read up a bit on the difference.. But am still a bit confused. Partial Application returns a function which acts like the function you pass in but which takes fewer arguments, the others having been "bound in". So, this is indeed partial application. However I don't understand why is it not currying? It seems like John Resig in "Secrets of JS Ninja" and Douglas Crawford in "JS: The Good parts" define the same function and named it "curry".. – henrybai Mar 22 '13 at 10:15

1 Answers1

3

The problem is that you've lost the context of the element. The addEventListener method has to be called on an element, but you're calling it on a function:

// Here, `this` refers to a function, not an element
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));

You would need to pass in the element to your new function. For example:

Function.prototype.curry = function () {
    var fn = this,
    args = Array.prototype.slice.call(arguments);

     return function (context) {
         return fn.apply(
             context,
             args.concat(Array.prototype.slice.call(arguments, 1))
         );
     }; 
};

Here's a working example. Notice the addition of a context argument to the returned function, and also notice the addition of the second argument to the slice call - that's needed to remove the new context argument and only apply any following arguments.

James Allardice
  • 164,175
  • 21
  • 332
  • 312
  • Oh okie. Thanks.. :) Just a clarification though, you mentioned that the `this` refers to a function. I guess that is because it is invoked by the fn method? Thus, in this cause, since fn refers to the addEventListener function, `this` would refer to the addEventListener method even though the anonymous function's `this` (invoked by bindClick() function) refers to the `window` object? – henrybai Mar 22 '13 at 09:51
  • 1
    @henrybai - Correct. But you need `this` to refer to an element, otherwise `addEventListener` won't have anything to actually apply to. – James Allardice Mar 22 '13 at 09:54
  • @henrybai: Yes, in `curry` the `this` refers to the addEventListener function - and is assigned to `fn` because we won't be able to use `this` in the anonymous function any more. Btw, it would be easier to use `bind`: `var bindClick = elem.addEventListener.bind(elem, "click");` – Bergi Mar 22 '13 at 09:57
  • @JamesAllardice @Bergi It seems like the `this` does refer window object as I did in my experiment here. Am I doing something wrong? http://jsfiddle.net/ya2hM/ – henrybai Mar 22 '13 at 10:42