I have a list of arguments such as var args = ['blah', 1, 3.9] and I want to apply it to something that needs to be newed like new bleh.Thinggy(a, b, c).
I want to do the following var m = {}; bleh.Thinggy.apply(m, args);
I am worried there is something I am not thinking of does anyone know if this is safe?
Your current method is flawed, because prototype inheritance will not work as expected.
The equivalent of method.apply(context, args) for constructors is:
// Given a list of arguments `args`:
var bindArgs = [Constructor.prototype].concat(args);
new (Function.prototype.bind.apply(Constructor, bindArgs));
The roles of Function.prototype.bind and Function.prototype.apply are explained at the corresponding documentation. Remember: .bind returns a function!
To keep it simple, I'll explain how to use .bind for a fixed number of arguments, say two. Then, the following have the same effect:
Math.max.apply(Math, 2, 3);
Math.max.bind(Math, 2, 3)();
And
Math.max.apply(Math, 2, 3, 4, 5);
Math.max.bind(Math, 2, 3)(4, 5);
If you've even glanced at the documentation, you'll certainly understand the first form. The second form is trickier though. It works in this case, because the position of an argument in Math.max is not relevant. The maximum value of all arguments is considered, where all arguments are treated identically.
Now, here follows an example with a custom function:
function echoMe(name, age) {
console.log('Hello ' + name + '. Your age is ' + age);
console.log('this is ', this);
}
echoMe('Rob', '19');
// "Hello Rob. Your age is 19"
// "this is [object DOMWindow]" (non-strict mode)
var echoYou = echoMe.bind(null, "Oops");
echoYou("Peter", "19");
// "Hello Oops. Your age is Peter"
// "this is null"
Because the position of arguments is significant in this case, the last example showed something weird. Indeed, the first argument is bound to "Oops" by the .bind method. The arguments passed to the bound function echoYou are appended to the arguments list. Additionally, you notice that the context this was changed to null.
Interesting.. Let's try to change the context using .apply:
function printThisFood() {
console.log("this.food is " + this.food);
}
printThisFood.apply({food: "Fish"});
// "this.food is fish"
var locked = printThisFood.bind({food: "Strong"});
locked.apply({food: "Weak"});
// "This.food is Strong"
As you can see, this.food still points to method from the context as defined through .bind!
So, we know how to lock the context of a function, as well as passing an arbitrary number of fixed arguments to a function. This can be applied to constructors, resulting in the function which I presented on top of the answer. To verify that it works as expected:
function Constructor() {
console.log(this instanceof Constructor); // true
console.log(this, arguments); // Convince yourself via console
}
var bindArgs = [Constructor.prototype].concat([1, 2]);
// is equal to [Constructor.prototype, 1, 2]
var BoundConstructor = Function.prototype.bind.apply(Constructor, bindArgs);
var instance = new BoundConstructor();
// Eliminated intermediate variable, and put on one line
var instance = new (Function.prototype.bind.apply(Constructor, bindArgs));
Note: I omitted parentheses () in the one-liner, because constructors can be initialized without these. new Image and new Image() are behaving identically.
To immediately read a property (or invoke a method) from the constructed method, you can either wrap the whole expression in parentheses or append () to remove ambiguity:
(new (Function.prototype.bind.apply(Constructor, bindArgs))).method()
new (Function.prototype.bind.apply(Constructor, bindArgs))().method();
Note 2: It still holds that additional arguments are appended to the argument list. This property can also be used to "preset" the first arguments of a given constructor:
function Stupid(obvious1, obvious2, foo) { this.interesting = foo; }
Stupid.prototype.onlymethod = function() { return this.interesting};
var saveKeyStroke = Function.prototype.bind.call(Stupid, Stupid.prototype, 1, 2);
// Or, equivalent:
//var saveKeyStroke=Function.prototype.bind.apply(Stupid,[Stupid.prototype,1,2]);
new saveKeyStroke('Fourth argument').onlymethod(); // "Fourth argument"
new saveKeyStroke().onlymethod(); // undefined
(new saveKeyStroke).onlymethod(); // undefined
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With