Liar

I’ve claimed in a couple talks recently that the ES6 expression

new Foo(...args)

is impossible to implement in ES3 and only became possible in ES5 with Function.prototype.bind:

Function.prototype.applyNew = function applyNew(a) {
    return new (this.bind.apply(this, [,].concat(a)))();
};
Foo.applyNew(args)

This works by closing the function over arguments array a with an undefined receiver (the [,] expression creates an array of length 1 but a hole at element 0). Since a function created by Function.prototype.bind ignores the bound receiver when called with new, this has the same behavior as the ES6 expression.

But I should not have counted ES3 out so easily — with the magic of eval, many impossible things are possible.

Function.prototype.applyNew = function applyNew(a) {
    return eval("new this(" +
                a.map(function(x, i) {
                          return "a[" + i + "]";
                      }).join(",") +
                ")");
};
Foo.applyNew(args)

Thanks to Trevor Norris for awakening me from my dogmatic slumber. His approach won’t work for functions like String that have different call and construct behavior, but he reminded me that I’d seen this solved before with eval.

Edit: Oops, the applyNew method doesn’t take a function argument, it uses this as the function. That’s what I get for posting without testing!

6 Responses to Liar

  1. Eh well, there is no Array.prototype.map in ES3? :))))

  2. I’ll let you implement that yourself in ES3. :-)

  3. Not even eva() is necessary to expand arguments in a constructor:

    function F() {}
    function expandArgs(constructor, args) {
    F.prototype = constructor.prototype;
    var o = new F();
    constructor.apply(o, args);
    return o;
    }

    CoffeScript uses this to implement new Foo(args…)

  4. Given a free var foo and ES5 or ES3 + any Crockfordesque create shim, how about:

    Foo.apply( foo = Object.create( Foo.prototype ), args ), foo

  5. This has the same issue that it invokes the [[Call]] internal method instead of the [[Construct]] internal method of the function.

  6. Stepping through the ES5 spec for [[Construct]], [[Call]], Object.create and (new Object) seems to indicate that both approaches cover the same ground internally as well. Is there something else I’m missing?