Skip to content

Instantly share code, notes, and snippets.

@jed
Created November 28, 2011 13:14
Show Gist options
  • Save jed/1400363 to your computer and use it in GitHub Desktop.
Save jed/1400363 to your computer and use it in GitHub Desktop.
an attempt to make the new operator dynamically invokeable
// usage: newOperator(constructor, [arg1, arg2, ...])
// example: newOperator(Date, [1995, 11, 24])
// => should be identical to new Date(1995, 11, 24)
function newOperator(constructor, args) {
var i = args.length + 1
, params = []
, fn
while (i--) params[i] = "$" + i
params.push("return new $0(" + params.slice(1) + ")")
fn = Function.apply(null, params)
params = params.concat.apply(constructor, args)
return fn.apply(null, params)
}
@WebReflection
Copy link

that won't work because the constructor most likely won't return "this" being implicit but definitively the evaluation is completely superfluous in the first place so you were close ... this will work as expected.

var newOperator = (function () {
  function anonymous() {}
  return function newOperator() {
    anonymous.prototype = constructor.prototype;
    var result = new anonymous;
    constructor.apply(result, args);
    return result;
  };
}());

In most modern browser you may want to do like this:

function newOperator(constructor, args) {
  var result = Object.create(constructor.prototype);
  constructor.apply(result, args);
  return result;
}

More JS patterns here: http://webreflection.blogspot.com/2011/11/few-javascript-patterns.html

@WebReflection
Copy link

padolsey you edited ... that's way better but I would ignore constructors that overwrite the returned instance ... it's about new, at least with a specific function you can fix that behavior expecting instances rather than objects 'cause in that case you don't even need the "new" or to create a new instance ... I would stuck with return result; ( and there is a typo in my second example, I wrote return constructor, lol )

@padolsey
Copy link

Sorry, yep, did edit. So, here's the same, but ignoring the returned instance:

function newOperator(constructor, args) {

    function F(){}
    F.prototype = constructor.prototype;

    var me = new F;
    constructor.apply(me, args);

    return me;

}

@padolsey
Copy link

@jed, use WebReflection's one. 'tis nicer (uses Object.create).

@padolsey
Copy link

Webreflection, our solutions don't work with Date, and I presume they'll also fail with String and Number. i.e. native constructors that have differing functionality dependent on whether they're called as constructors or functions. E.g.

Will fail:

newOperator(String, ['foo']);
newOperator(Date, [1995, 11, 24]);

@jed
Copy link
Author

jed commented Nov 28, 2011

@padolsey: yeah, that was my point. i want something that will work for any constructor, including native ones. Date is by far the trickiest.

@padolsey
Copy link

DavidBruant's fork looks good (although relies on bind).

@DavidBruant
Copy link

Yup. I don't think it's possible without a native bind. In a recent talk I think @dherman mentionned that.

@jed
Copy link
Author

jed commented Nov 28, 2011

if i cache the dynamically generated function for each arguments length, i think this could be acceptable.

@DavidBruant
Copy link

Basically, what we want here is to access the [[Construct]] internal method of a function and call it with an arbitrary array of arguments. There is no way to do that with ES3. In ES3, there is one "type" of functions and the only way to access the [[Construct]] is to call "new".
ES5 brings in bound functions with a bound |this| and bound arguments. Bounds arguments are used both by the [[call]] and the [[construct]].
ES6 will make things even easier with the spread operator http://wiki.ecmascript.org/doku.php?id=harmony:spread: var c = new C(...args)

@ryantenney
Copy link

@padolsey I use a method extremely similar to the one in your 2nd comment. Works perfectly for me.

@jed
Copy link
Author

jed commented Nov 28, 2011

this is probably a little less complex:

function newOperator(ctor, args) {
  var i, l = args.length, body = "return new ctor("

  for (i = 0; i < l; i++) {
    if (i) body += ","
    body += "args[" + i + "]"
  }

  return Function("ctor", "args", body + ")")(ctor, args)
}

@WebReflection
Copy link

right, I guess we both missed the whole point then ... too bad

function newOperator(C, a) {
  return Function(
    "C,a,i",
    "return new C(" +
      Array(a.length + 1).join(",a[i++]").slice(1)
    + ")"
  )(C, a, 0);
}

@jed
Copy link
Author

jed commented Nov 28, 2011

with caching:

function newOperator(ctor, args) {
  var l = args.length
    , fn = newOperator[l]
    , body, i

  if (!fn) {
    for (i = 0; i < l; i++) {
      i ? body += "," : body = "return new ctor("
      body += "args[" + i + "]"
    }

    fn = newOperator[l] = Function("ctor", "args", body + ")")
  }

  return fn(ctor, args)
}

@jed
Copy link
Author

jed commented Nov 28, 2011

holy cow, you can put commas in the argument string? does that work universally?

@WebReflection
Copy link

sure it does ;-)

@WebReflection
Copy link

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function

Names to be used by the function as formal argument names. Each must be a string that corresponds to a valid JavaScript identifier or a list of such strings separated with a comma; for example "x", "theValue", or "a,b".

@WebReflection
Copy link

with cache ...

function newOperator(g, o, l, f) {
  return ((f = newOperator)[l = (o || []).length] || (f[l] = Function(
    "g,o,l",
    "return new g(" +
      Array(l + 1).join(",o[l++]").slice(1)
    + ")"
  )))(g, o, 0);
}

@jed
Copy link
Author

jed commented Nov 28, 2011

bravo, @WebReflection. i'm probably going to go with something like this:

function newOperator(ctor, args) {
  var l = args.length + 1
    , fn = newOperator[l]

  fn || (fn = newOperator[l] = Function(
    "a,b,c",
    "return new a(" +
      Array(l).join(",b[c++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

does not fit into 140 bytes ... shame of us :D

@jed
Copy link
Author

jed commented Nov 28, 2011

actually, it's 129 bytes: https://gist.github.com/1400834

@WebReflection
Copy link

you changed function name, if it's d of course it's smaller ... I would still leave the (a||[]).length to make newOperator(Array) possible, as example

@jed
Copy link
Author

jed commented Nov 28, 2011

good point, fixed it.

@jed
Copy link
Author

jed commented Nov 28, 2011

a non-golfed version:

function newOperator(ctor, args) {
  var length, fn

  args || (args = [])
  length = args.length + 1
  fn = newOperator[length]

  fn || (fn = newOperator[length] = Function(
    "ctor", "args", "i",
    "return new ctor(" +
      Array(length).join(",args[i++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

jed, no need to b=b||[] ... just b||[] because if length is zero Array(1).join("whatever") will be an empty string (join works with at least length 2 between 0 and 1) so the b, even if undefined, will be untouched ;-)

function d(
  a, // constructor function
  b, // arguments
  c  // placeholder for length
){
  return(                         // return the result of
    d[c=b?-~b.length:1] || (      // the cached function or
      d[c] = Function(            // a new function that takes
        "a,b,c",                  // a constructor, arguments, and an index
        "return new a(" +         // and returns a new instance of the constructor
          Array(c)                // with
            .join(",b[c++]")      // the inline arguments
            .slice(1) +           // (sliced to remove leading comma)
        ")"
      )
    )
  )(a,b,0)                        // called with the constructor and arguments
}

@jed
Copy link
Author

jed commented Nov 29, 2011

in that case, why define the array at all if args has no length? can we use this?

var length = args ? args.length + 1 : 0

[EDIT] so something like this:

function newOperator(ctor, args) {
  var length = args ? -~args.length : 0
    , fn = newOperator[length]

  fn || (fn = newOperator[length] = Function(
    "ctor", "args", "i",
    "return new ctor(" +
      Array(length).join(", args[i++]").slice(1) +
    ")"
  ))

  return fn(ctor, args, 0)
}

@WebReflection
Copy link

the minus tilde does not give us much more than +1, 2 readable chars with no necessary int cast.
I agree the creation of an empty Array per each call without arguments is way too redundant and also bigger than this solution:
d[c=b?b.length+1:1]
I have edited and as result 2 chars less and faster "no arguments" execution ;-)

@jed
Copy link
Author

jed commented Nov 29, 2011

doesn't leaving out the number cast mean that the length property could be used to inject code? i'm thinking where args is {length: "throw 'neener neener'"}.

@WebReflection
Copy link

well, the initial trick does not solve a thing here ... if you want to screw up functions that would work even with native arrays methods ...

@WebReflection
Copy link

uh .. wait, I know what you mean ... so yes, minus tilde then, updated :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment