Mimicking bind, call and apply post


Update

This developer recently discovered a proposed JavaScript operator and you won't believe what happens next! Check it out here!!


Recently, I've been working on a presentation about JavaScript's this. this is a really interesting topic and the preparation has got me thinking a lot about context and how it's applied within the execution of a JavaScript function.

I wanted to see if I could mimic the bind, call and apply methods of Function.prototype from first principles. I wasn't sure if this was even going to be possible as this in a functions execution context is really well protected. In the end, bind turned out to be my work horse, whether using bind in the implementation of these functions is considered cheating is for you to decide.

bind

bind was the simplest method to mimic. When you call bind, all it really does is return a function that remembers the context you passed it. This is so that when you invoke the function, it executes within the remembered context. Doing this is really easy to accomplish using closures.

function bind(context, fn) {
    return function () {
        var args = [].slice.call(arguments, 0);
        return fn.apply(context, args);
    };
}

function aWhatSaysWhat() {
    console.log("A " + this.what + " says " + this.sound + ".");
}

var fox = {
    what: 'fox',
    sound: 'ring-ding-ding-ding-dingeringeding'
},
    whatDoesTheFoxSay = bind(fox, aWhatSaysWhat);

whatDoesTheFoxSay();
// => A fox says ring-ding-ding-ding-dingeringeding.

call

call was certainly more difficult to wrap my head around. The biggest difficulty I faced was unpacking the arguments passed to the called function. I ended up having to scrap work on one implementation because, in the end, it was too "cheaty". I think though that the process I went through is interesting and important to understanding the final "not so cheaty" solution.

I started by creating a function called partialFirst. It accepted a function and any other single value as its arguments. The return value of partialFirst is a closure which, when invoked, prepends the first value to the list of arguments the closure was invoked with and then returns the result of the closed over function invoked using those arguments.

function partialFirst(fn, first) {
    return function () {
        var args = [].slice.call(arguments, 0);
        args.unshift(first);
        return fn.apply(this, args);
    };
}

My idea was to use this function to create a nesting of partial functions where each partial would be passed one of the arguments used in the invocation of my call. Essentially, this would wind partial functions around the target function. When the outer most partial is executed it would trigger an unwinding of partials where each partial would add its own closed over first argument to an array of final arguments. This array of arguments would then be applied to the target function and the result of the invocation would be returned. It worked surprisingly well.

function call(context, fn) {
    var i, l = fn.length,
        args = [].slice.call(arguments, 2);

    return (function () {
        for (i = 0; i < l; i++) {
            fn = partialFirst(fn, args[i]);
        }
        return fn.bind(context)();
    })();
};

weSay = {
    you: 'goodbye',
    me: 'hello'
};

function helloGoodbye(youSay, iSay) {
    console.log("You say " + youSay + ", I say " + iSay);
}

function objHelloGoodbye() {
    console.log("You say " + this.you + " and I say " + this.me);
}

call(void 0, helloGoodbye, 'yes', 'no');
// => 'You say yes, I say no'
call(weSay, objHelloGoodbye);
// => 'You say goodbye and I say hello'

I was pretty happy with myself when I sorted this out and honestly a little worried that it appeared to be working. I came back to it a couple time to make sure I actually did know how it was working and why. To tidy things up, I decided to make partialFirst an inner function inside my call function. That's when I discovered that I took a complicated approach in an attempt to hide the cheating that what going on.

I was using apply to implement call and if I was going to do that, a much less complicated solution would be:

function call(context, fn) {
    var args = [].slice.call(arguments, 2);
    return fn.apply(context, args);
}

and while that's simple and small, it's still cheating. I needed to find an alternate solution.

While I was looking for this elusive alternate solution I circled back to bind and discovered that my original implementation was incomplete. It turns out that bind also accepts a varying amount of arguments which it prepends to any arguments the bound function is called with. This is what I needed, partialFirst was basically a hobbled version of bind. I updated my call to use bind and removed partialFirst.

function call(context, fn) {
  var i, l = fn.length,
      args = [].slice.call(arguments, 2),
      fn = fn.bind(context);

  return (function () {
    for (i = 0; i < l; i++) {
      fn = fn.bind(context, args[i]);
    }
    return fn();
  })();
};

Much better.

apply

Since the difference between call and apply is how the arguments are passed to the target function, once I had call sorted out, mimicking apply was super simple.

function apply(context, fn, args) {
  var i, l,
      fn = fn.bind(context);

  return (function () {
    if (args) {
        for (i = 0, l = args.length; i < l; i++) {
          fn = fn.bind(context, args[i]);
        }
    }
    return fn();
  })();
};

The only place I got tripped up was if the target function wasn't given any arguments, this is why I'm verifying that args is defined before winding the target function up with partials.

A better bind

I noted earlier that my original mimic of bind was incomplete, let's resolve that now.

function bind(context, fn) {
    function mergeArgs (boundArgs, callArgs) {
        function push (elem) {
            this.push(elem);
        }
        var merged = [];
        boundArgs.forEach(push, merged);
        callArgs.forEach(push, merged);
        return merged;
    }

    var boundArgs = [].slice.call(arguments, 2);
    return function () {
        var callArgs = [].slice.call(arguments, 0)
        return fn.apply(context, mergeArgs(boundArgs, callArgs));
    };
}

function greet(greeting, name) {
    console.log(greeting + " " + name + "!");
}

var morningGreeter = bind(void 0, greet, 'Good Morning');

morningGreeter('Alice');
// => Good Morning Alice!

morningGreeter('Bob');
// => Good Morning Bob!

The better bind was a little more involved but not by much. I think I may have gotten a little clever in the mergeArgs function but I'll leave that for you to decide.

All in all, this was a really fun exercise and I feel like I came out of it with a better understanding of how context can be manipulated in JavaScript. A total win win for me.

Categories: javascript

Tags: javascript, interesting but not useful