Alex Wayne
Alex Wayne

Reputation: 187222

Understanding Javascript's difference between calling a function, and returning the function but executing it later

I'm trying to understand the difference between foo.bar() and var fn = foo.bar; fn();

I've put together a little example, but I dont totally understand why the failing ones actually fail.

var Dog = function() {
    this.bark = "Arf";
};

Dog.prototype.woof = function() {
    $('ul').append('<li>'+ this.bark +'</li>');
};

var dog = new Dog();


// works, obviously
dog.woof();

// works
(dog.woof)();

// FAILS
var fnWoof = dog.woof;
fnWoof();


// works
setTimeout(function() {
    dog.woof();
}, 0);

// FAILS
setTimeout(dog.woof, 0);

Which produces:

On JSFiddle: http://jsfiddle.net/D6Vdg/1/

So it appears that snapping off a function causes it to remove it's context. Ok. But why then does (dog.woof)(); work?

It's all just a bit confusing figuring out whats going on here. There are obviously some core semantics I'm just not getting.

Upvotes: 5

Views: 409

Answers (4)

lomaxx
lomaxx

Reputation: 115833

This is a particularly confusing bit of code for a number of reasons that I'll try to explain.

First... the setTimeOut method is a bit of a PITA as explained here: http://ifhere.org/javascript

The issue you're having with the setTimeout(dog.woof, 0); is because of setTimeOut, not your code (well not entirely your code).

The main issue tho is that the dog.woof is getting executed, it's the value of bark that is undefined and the reason it's undefined is because you are passing the function itself as the parameter, not the function as it is attached to the variable dog.

in both:

var fnWoof = dog.woof;

and

setTimeout(dog.woof, 500);

you are passing the function definition as opposed to the instantiated object and it's associated function.

Upvotes: 0

Mike Samuel
Mike Samuel

Reputation: 120546

In foo.bar(), the function is called with the value of foo as the value of this. With var fn = foo.bar; fn(); the function is called with null as the value of this which is automatically coerced to the global object when this is evaluated.

The relevant portion of the spec is below. See especially 7. If it's a property reference (i.e. an expression like a.b or a[b]), then thisValue becomes the base of the property reference.

11.2.3 Function Calls

The production CallExpression : MemberExpression Arguments is evaluated as follows:

  1. List item
  2. Let ref be the result of evaluating MemberExpression.
  3. Let func be GetValue(ref).
  4. Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).
  5. If Type(func) is not Object, throw a TypeError exception.
  6. If IsCallable(func) is false, throw a TypeError exception.
  7. If Type(ref) is Reference, then

a. If IsPropertyReference(ref) is true, then Let thisValue be GetBase(ref).

b. Else, the base of ref is an Environment Record so Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).

Upvotes: 0

jon_darkstar
jon_darkstar

Reputation: 16778

dog.woof has to be run in the context of an instance of Dog because it depends on this.bark, as you seem to understnad

Dog.prototype.woof2 = function() {$('ul').append('<li>'+ 'arf' +'</li>'); }; 

If you replace those failing woof with woof2 it could run anywhere

As for the case (dog.woof)(); vs var fnWoof = dog.woof; fnWoof(); it is kind of counterintuitive. The difference is the assignment. The first case still runs it as belonging to dog, whereas the second does not. It is assignment time that takes woofand puts it into a different constant, not merely accessing it.

Upvotes: 0

David Tang
David Tang

Reputation: 93694

The problem is with context and the this keyword.

Functions don't inherently "belong" to an object. For example, I can create a cat object and copy the woof function over:

var cat = {
    bark: "meow",
    woof = Dog.prototype.woof
};

Now cat.woof will give me "meow". Because of this flexibility of functions to be copied and re-assigned, var fnWoof = dog.woof; disassociates fnWoof from dog - it has no context. Therefore the context, and the this keyword, defaults to window. Since window doesn't have a bark property, you get undefined.

If you give window a bark property:

window.bark = "Arf";

Then your code will work (although erroneously):

fnWoof(); // "Arf"

To make it work as expected, you can pass in the context explicity:

fnWoof.call(dog);

Upvotes: 5

Related Questions