user2294256
user2294256

Reputation: 1049

Understanding Function.prototype.apply

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:

  1. I know that if I use var myInstance = new MyConstructor(); it will invoke MyConstructor(), but how does var myInstance = MyConstructor.construct(myArray); invoke MyConstructor()?

  2. 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

Answers (4)

StackSlave
StackSlave

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

Qantas 94 Heavy
Qantas 94 Heavy

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 (and Function 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 in Function.prototype.

I've split my answer into two parts, Question 1 and Question 2:

Question 1

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).

Question 2

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

bfavaretto
bfavaretto

Reputation: 71918

On your first question:

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).

On your second question:

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

SLaks
SLaks

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

Related Questions