Reputation: 1049
The following code is taken from the MDN page about Function.prototype.apply
:
Function.prototype.construct = function (aArgs) {
var fConstructor = this,
fNewConstr = function () { fConstructor.apply(this, aArgs); };
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
};
function MyConstructor() {
for (var nProp = 0; nProp < arguments.length; nProp++) {
this["property" + nProp] = arguments[nProp];
}
}
var myArray = [4, "Hello world!", false];
var myInstance = MyConstructor.construct(myArray);
alert(myInstance.property1); // alerts "Hello world!"
alert(myInstance instanceof MyConstructor); // alerts "true"
alert(myInstance.constructor); // alerts "MyConstructor"
I have two questions about this code:
I know that if I use var myInstance = new MyConstructor();
it will invoke MyConstructor()
, but how does var myInstance = MyConstructor.construct(myArray);
invoke MyConstructor()
?
MyConstructor.construct(myArray);
was called as a method of MyConstructor
, but that method was declared as Function.prototype.construct
, not MyConstructor.prototype.construct
. What is the difference between Function.prototype.construct
and MyConstructor.prototype.construct
?
Upvotes: 3
Views: 5829
Reputation: 10627
You have now created a construct
method for every Function
, since you accessed it with Function.prototype
. fConstructor
is a misleading variable, since this
is assigned to it, which is actually referring to every Function
. Inside a method that is a property of an Object this
really refers to the Object to which the method belongs. fNewConstr
would have to be this.fNewConstr
to refer to every Function
. In this case it's really a variable that holds a reference to a Function
which later becomes part of the Function
prototype with fNewConstr.prototype = fConstructor.prototype
, which is later executed with new fNewConstr()
. When new fNewConstr()
is called it uses apply()
to pass fConstructor
, or every Function
an Array of arguments. So your passing the Array to MyConstrutor
.
Upvotes: 2
Reputation: 16020
TL;DR:
Q1: It's called via the nested function, which itself is called by the
new fNewConstr()
call. It's just to allow a list of arguments to be passed as an array without modifying how the function actually works with its arguments.
Q2: Anything on a prototype is accessible by all instances of that constructor (andFunction
is the constructor of all native functions in JavaScript), but MyConstructor is not an object instance of itself, which is why it needs to be defined inFunction.prototype
.
I've split my answer into two parts, Question 1 and Question 2:
Function.prototype.construct is meant to allow the passing of an array to act as an arguments list, without the use of Function.prototype.bind
. It calls the original function inside a nested function and the passed arguments as an array, and the prototype set to the original function's prototype.
In the code given, what Function.prototype.construct
method does is the following
Function.prototype.construct = function (aArgs) {
var fConstructor = this,
This first line allows us to access the function which is being called - the value of this
(the ThisBinding in specification speak) in that scope is the function that we want to call.
fNewConstr = function () { fConstructor.apply(this, aArgs); };
This next line will call the original function with Function.prototype.apply
, which allows the arguments to that function to be passed as an array. The reason why this
is passed as the ThisBinding is to make assignments to properties of this
go to the ThisBinding of the called function, which in this case will be the new object created by the "new" operator.
fNewConstr.prototype = fConstructor.prototype;
This simply makes the prototype of the returned object created by the "new" operator the same as the calling function, as the "new" operator is called on the new constructor, not the original one.
return new fNewConstr();
This is pretty much self-explanatory - it creates a new object with the associated values created by the constructor on the properties of this
or returns the object returned by the function, if there is any.
What is returned is the same as if new MyConstructor()
had been directly called, except for the way the arguments were given (just like how Function.prototype.call()
and Function.prototype.apply()
both exist). For example, these two code examples are equivalent:
new MyConstructor(4, "Hello world!", false); // { property0: 4, property1: 'Hello world!', property2: false }
MyConstructor.construct([4, "Hello world!", false]); // { property0: 4, property1: 'Hello world!', property2: false }
... just like how these are equivalent:
function foo(a, b, c, d, e) { return a + b + c + d + e; }
foo.call(null, 1, 2, 3, 4, 5); // 15
foo.apply(null, [1, 2, 3, 4, 5]); // 15
That's the only difference between the two - one simply calling the constructor with an arguments list, another with the arguments list represented as an array (the constructor function itself still gets the arguments as a list, not an array).
All instances of functions borrow from the Function
prototype, so any methods (or for that matter, property) defined on Function.prototype
will be available to all functions. Take this example:
function foo() { return 1 + 1; }
Function.prototype.bar = function ()
{ var result = this();
if (typeof result === 'number' && isFinite(result)) return result + 0.5;
return NaN;
};
foo.bar(); // 2.5
However, when you declare a method on MyConstructor.prototype
, it's only available to instances of MyConstructor
, not MyConstructor
itself, as shown below:
function MyConstructor(num) { this.x = 1 + num; }
MyConstructor.prototype.bar = function () { return 2 + 2; };
MyConstructor.bar(); // TypeError: MyConstructor.bar is not a function
MyConstructor.x; // undefined
See how that doesn't work? You need to use it on an instance of MyConstructor
:
var foo = new MyConstructor(4);
foo.x; // 5
foo.bar(); // 4
If it's on a prototype, any edit to the prototype will affect all prototype methods/values on an object:
function MyConstructor(num) { this.x = 1 + num; }
MyConstructor.prototype.bar = function () { return 2 + 2; };
var foo1 = new MyConstructor(6);
foo1.bar(); // 4
MyConstructor.prototype.bar = function () { return 3 + 3; };
var foo2 = new MyConstructor(9);
foo2.bar(); // 6
foo1.bar(); // 6
Keep in mind though if you make a new prototype object completely, the instances created before the change will still reference the old object:
var foo1 = new MyConstructor(6);
foo1.bar(); // 4
MyConstructor.prototype = { bar: function () { return 3 + 3; } };
var foo2 = new MyConstructor(9);
foo2.bar(); // 6
foo1.bar(); // 4?!
Just remember though that properties declared directly on MyConstructor
won't be passed on to instances:
MyConstructor.bar2 = function () { return 42; }; // the answer to everything?
MyConstructor.y = 3.141592653589793238462643383279502881197169399; // I only can remember that much
MyConstructor.bar2(); // 42, as you'd expect
var foo = new MyConstructor(99);
foo.bar2(); // TypeError: foo.bar2 is not defined
foo.y; // undefined
Prototype-based programming can be quite powerful (and in fact I much prefer it to classes, but that's a different story), but you need to understand what it does. When used properly, you can do really great things with it - keep learning! :)
Upvotes: 11
Reputation: 71918
MyConstructor
is called through a series of indirections, and passed an array as its arguments list. When MyConstructor.construct(myArray)
is called, this
inside construct
will be MyConstructor
. A reference to that is then stored as fConstructor
, and a child constructor fNewConstr
is created. That constructor invokes MyConstructor
with apply
, passing the elements from myArray
as the arguments.
As a side note, that seems like an overcomplicated way to explain Function.prototype.apply
(MDN says it's for mimicking Java, so that might explain it).
When construct
is added to Function.prototype
, it becomes available on all function objects (instances of Function
), including MyConstructor
. If you add to MyConstructor.prototype
you affect all instances of MyConstructor
, and when you add to Function.prototype
you extend all function objects.
Upvotes: 1
Reputation: 887385
The answer to both of your questions is that this
inside of construct()
is set based on how you call it.
If you write Function.prototype.construct()
, this
will be Function.prototype
, and it will break.
When you write MyConstructor.construct()
, this
is MyConstructor
, so the code will work.
As a side note, this code can be greatly simplified in supporting browsers:
Function.prototype.construct = function (args) {
return new (this.bind.apply(this, [null].concat(args)))();
};
Upvotes: 2