-
-
Save jed/1400363 to your computer and use it in GitHub Desktop.
// 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) | |
} |
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 )
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;
}
@jed, use WebReflection's one. 'tis nicer (uses Object.create
).
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]);
@padolsey: yeah, that was my point. i want something that will work for any constructor, including native ones. Date
is by far the trickiest.
DavidBruant's fork looks good (although relies on bind
).
Yup. I don't think it's possible without a native bind. In a recent talk I think @dherman mentionned that.
if i cache the dynamically generated function for each arguments
length, i think this could be acceptable.
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)
@padolsey I use a method extremely similar to the one in your 2nd comment. Works perfectly for me.
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)
}
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);
}
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)
}
holy cow, you can put commas in the argument string? does that work universally?
sure it does ;-)
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".
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);
}
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)
}
does not fit into 140 bytes ... shame of us :D
actually, it's 129 bytes: https://gist.github.com/1400834
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
good point, fixed it.
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)
}
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
}
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)
}
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 ;-)
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'"}
.
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 ...
uh .. wait, I know what you mean ... so yes, minus tilde then, updated :D
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.
In most modern browser you may want to do like this:
More JS patterns here: http://webreflection.blogspot.com/2011/11/few-javascript-patterns.html