Reputation: 187222
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
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
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.
The production CallExpression : MemberExpression Arguments is evaluated as follows:
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
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 woof
and puts it into a different constant, not merely accessing it.
Upvotes: 0
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