A Coder’s Corner

Blog de tecnologia y programación

Boost.Bind Ala Javascript

Posted by gfaraj on January 18, 2008

I’ve recently started learning Javascript. As a C++ programmer, I thought I’d write a Javascript version of Boost.Bind; just for fun. Javascript closures make this a fairly simple task, actually.

Let’s get right into the code.

 
function bind(f)
{
    if (f === null)
        return function() {};

    var args = Array.prototype.slice.call(arguments);
    args.shift();

    return function ()
    {
        var argsCopy = args.slice(0);
        var start = expandArgs(argsCopy, arguments);
        return f.apply(null, argsCopy.concat(Array.prototype.slice.call(arguments, start)));
    };
}

Simple enough? The expandArgs function will be shown at the end. Now for some example usage.

function foo(a, b, c)
{
    alert(a + ', ' + b + ', ' + c);
}

bind(foo, 1)(2, 3);  // 1, 2, 3 bind(foo, 1, 2)(3);  // 1, 2, 3 bind(foo, _3, _2, _1)(1, 2, 3);  // 3, 2, 1 bind(foo, _2, _2, _2)(1, 2, 3);  // 2, 2, 2 bind(foo, _2, 10)(1, 2, 3);  // 2, 10, 3

Another function, called bindMember, will bind a function to an object, so the this object refers to it. It also does the same parameter binding as the original bind.

 
function bindMember(f, o)
{
    if (f === null)
        return function() {};
    else if (o === null)
        return f;

    var args = Array.prototype.slice.call(arguments);
    args.shift();
    args.shift();

    return function ()
    {
        var start = 0;

        if (o.constructor == BindArg)
        {
            var arg = o.index;
            o = arguments[arg - 1];
            if (arg > start)
                start = arg;
        }

        var argsCopy = args.slice(0);
        start = expandArgs(argsCopy, arguments, start);
        return f.apply(o, argsCopy.concat(Array.prototype.slice.call(arguments, start)));
    };
}

As you can see, it’s very similar to bind, except it does an extra check to see if the object passed was a bind-argument. Here are some examples of its usage.

var o = { name : 'someobject' };

function foo(a, b, c)
{
    alert(this.name + ', ' + a + ', ' + b + ', ' + c);
}

bindMember(foo, o, 1)(2, 3);  // someobject, 1, 2, 3 bindMember(foo, o, 1, 2)(3);  // someobject, 1, 2, 3 bindMember(foo, o, _3, _2, _1)(1, 2, 3);  // someobject, 3, 2, 1 bindMember(foo, _1, 10, _2, _3)(o, 1, 2);  // someobject, 10, 1, 2

That’s it. Be aware that this was merely done as an experiment, with the purpose of learning Javascript. I have not done any benchmarks to study the efficiency of these functions. If anyone has time to do them, please let me know the results.🙂

Note that you could also make these functions part of the Function prototype, if you wish. That would allow writing foo.bind(1)(2, 3) if you find that more readable.

The rest of the code is as follows:

function BindArg(i)
{
    this.index = i;
}

for (var i=1; i < 10; ++i)
{
    var arg = new BindArg(i);
    this['_' + i] = arg;
}

function expandArgs(args, arguments, start)
{
    if (start == null)
        start = 0;

    for (var i=0; i < args.length; ++i)
    {
        if (args[i] && args[i].constructor == BindArg)
        {
            var arg = args[i].index;
            if (arg > 0 && arg <= arguments.length)
            {
                args[i] = arguments[arg - 1];
                if (arg > start)
                    start = arg;
            }
            else                 args[i] = null;
        }
    }

    return start;
}

4 Responses to “Boost.Bind Ala Javascript”

  1. Robert said

    Wow, I love Boost.Bind and the fact that I can use this in Javascript is wonderful. I used your code in my project and it works great.

    One small bug fix I made, though. In your expandArgs() method, it fails if any of the arguments are ‘undefined’, so I simply modified:

    if (args[i].constructor == BindArg)

    to:

    if (args[i] && args[i].constructor == BindArg)

    Now it works perfectly🙂

    Thanks again!

    • gfaraj said

      Robert, thanks for the bug fix! I’m glad you like the code. I’ve updated the code with your bug fix, and one other bug fix as well. You might want to check it out.

  2. jerros said

    I stumbled across this while trying to figure out how to replicate boost::bind() in javascript. I’m new to javascript so this was a big help, I did however find a bug with the code which I have yet to find a solution for. The late binding provided by the bind() seems to store it’s initial parameters over the lifetime of the object which is a bit of a problem.

    I’m working off memory here and no js runtime available so the code might not be perfect but I think I can show the bug fairly easily.

    function myClass(boundFunctionPointer)
    {
    this._boundFunctionPointer = boundFunctionPointer;
    function getBoundFunctionPointer(string) function() ( this.boundFunctionPointer(string); }
    }

    function printText(string)
    {
    print(string + “\n”);
    }

    var classInstance = new myClass(bind(printText(_1));
    classInstance.getBoundFunctionPointer(“test1”); // Outputs “Test1”
    classInstance.getBoundFunctionPointer(“test2”); // Outputs “Test1” but should output “test2” if it worked like boost bind.

    • gfaraj said

      Jerros, you are absolutely right. That’s a bug! I’ve found the solution though. I’ve modified the code to take care of it. I needed to copy the args array before modifying it. Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: