Elias Van Ootegem
Elias Van Ootegem

Reputation: 76395

JavaScript's this keyword surprise

I thought I knew how JavaScript's this keyword worked, but I've been caught by surprise again. Considering this snippet:

function foo()
{
    return 'Foobar';
}
foo.valueOf = function()
{
    return this();//this points to foo
};
foo.toString = foo;//alternatively
console.log(foo + '');//logs Foobar as you'd expect

In the valueOf method, this will point to the function object, because I'm defining a property of the function object. But when I try to do the same thing with the location object:

location.origin = function()
{
    return this.protocol + '//' + this.hostname;
};
location.origin.valueOf = location.origin;
location.origin.toString = function()
{
    return this();
}
console.log(location.origin + '/uri');//undefined//undefined/uri ?
console.log(location.origin.toString());//undefined//undefined ?
console.log(location.origin.valueOf());//undefined//undefined ?

The only way to get this to work is changing this() to location.origin(). Could anybody explain what is so different about the location object? I can just assign properties and methods at will, but I have noticed the Location constructor and its prototype is not as "accessible" as the other prototypes. In Chrome you have to use Object.getPrototypeOf(location);, whereas FF allows Location.prototype.

Basically, I have 2 questions:
What's the difference between the location.origin stuff above and:

var foo = {bar:function(){return 'Foobar';}};
foo.bar.valueOf = function(){return this();};
console.log(foo.bar + '');//logs Foobar!

And secondly
Are there any other objects that behave like this?

Upvotes: 1

Views: 198

Answers (3)

RobG
RobG

Reputation: 147413

The value of this is set entirely by how a function is called, or by Function.prototype.bind.

In the valueOf method, this will point to the function object, because I'm defining a property of the function object.

No, it isn't. In the function, this references foo because of how you called the function, not how you defined it.

> location.origin = function() {
>     return this.protocol + '//' + this.hostname;
> };
>
> location.origin.valueOf = location.origin;

Note that location is a host object. In Safari, origin is readonly, the above does nothing:

alert(typeof location.origin); // string, not function

The result in Firefox is different, it's as noted in the OP.

A golden rule in javascript is: "don't treat host objects like native objects". That's because they don't necessarily behave like native objects. The behaviour you observe has nothing to do with how this is set and everything to do with messing with host objects and their properties.

Upvotes: 2

balafi
balafi

Reputation: 2153

foo.valueOf does not point to 'foo' but to 'Foobar'. this is because of return this(); // parenthesis after this means that foo is executed and its result (=foobar) is finally returned

In the second example, location.origin.valueOf is a function

location.origin = function()
{
    return this.protocol + '//' + this.hostname;
};
location.origin.valueOf = location.origin();  //<-- Note the parenthesis here
location.origin.toString = function()
{
    return this();
}
console.log(location.origin() + '/uri'); //<-- again parenthesis here
console.log(location.origin.toString);// function
console.log(location.origin.valueOf);  //<-- parenthesis removed here

Upvotes: 0

Svend
Svend

Reputation: 8158

I'm thinking that since window.location is a host object, it does not obey "native" JS object semantics, and that's why you're having problems doing the same thing for location as you did with foo.

http://jibbering.com/faq/#nativeObject

Upvotes: 0

Related Questions