1

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?

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Please read the answer [here](http://stackoverflow.com/questions/12521148/javascript-objects-specifically-this-keyword/12522386#12522386) and see if it clears up some of your confusion. – RobG Sep 21 '12 at 14:04

3 Answers3

2

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.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • sorry, but I don't see what the difference between `foo.valueOf` and `location.origin.valueOf` is. Tried `var foo = function(){return 'Foobar';};`, too, and it makes no difference. Functions are objects, assigning properties to objects --> this points to the object, it works for functions, and methods every were else, but not for `origin.location`. I don't see why. In 99.99% of cases, I know what this is pointing to, and why, I guess this is the .01% – Elias Van Ootegem Sep 21 '12 at 14:12
  • I've noticed FF defines the location object as a WrappedNativeObject or sth, and doesn't allow you to augment its prototype. Webkit browsers do have the origin property, so chrome and safari don't need this property to be defined manually. Haven't checked IE, because I woke up this morning, feeling sick, and IE tends to make things worse, not better. Thx for the heads up – Elias Van Ootegem Sep 21 '12 at 16:51
  • I'm accepting this as the answer, because of the _native objects_ bit. Just one side-note: when I say _this refers to the function object_, I meant `foo`, being an instance of the function object. So I don't get what you're saying here: _No, it isn't. In the function, this references foo because of how you called the function, not how you defined it._ – Elias Van Ootegem Sep 22 '12 at 09:51
  • I mean that `this` is set entirely by how you call a function, not how you define it. If you call a function as a method of an object, then that sets its `this`. The difference between `foo()` and `widnow.foo()` is that in the first case, `this` defaults to `window` (and will be undefined in strict mode) because it isn't set by the call, whereas in the second case, `this` is explicitly set to `window` by the call. – RobG Sep 24 '12 at 00:03
  • I know that, but because I assigned the anonymous function as the `valueOf` property, `this` will always be the call context (except for explicitly using `call(window);` or `apply(window,[]);`). – Elias Van Ootegem Sep 24 '12 at 07:36
  • [Context](http://ecma-international.org/ecma-262/5.1/#sec-10) in javascript relates to an execution context, `this` is a component of an execution context, it doesn't define it. If you do `var x = location.origin.valueOf; x()` then `this` will default to `window`. Both calls are to the same function and can be from the same (execution) context but `this` will be different. – RobG Sep 24 '12 at 12:19
  • Sorry, but this is going a off-topic a bit: like you stated in your answer, my question has little to do with `this`, but more with _WrappedNative prototype objects_. No need to explain to me what `this` references and why (I do know about `this`). I've read up a little on FF and its way of dealing with WrappedNative objects (like host). I appreciate the effort, but I can be a bit of a stubborn mule at times. I understand about context, closures, references prototypes, this... I had just always managed mess with native objects, too, up until now that is. – Elias Van Ootegem Sep 24 '12 at 12:52
  • just as a side-note: my code works on chrome, IE (even IE8!), no problem: `location.foo = function(){return 'bar';}; location.foo.valueOf = location.foo` or even `location.foo.valueOf = function(){return this();};` both log `bar` when called implicitly (`location.foo + '';`). Apparently FF is the one with the issues in this case :) – Elias Van Ootegem Sep 24 '12 at 13:11
  • 1
    Cool. There is an article by Nicholas Zakas [Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/) that seems relevant. People often complain about one browser or another being different to the rest, but that will always be the case so just be defensive and not expect them to be consistent, especially for non–specified behaviour. – RobG Sep 24 '12 at 23:13
  • thanks for the link, I've had a quick read through the article. Well written, clear and to the point. I'll definitely be taking these simple guidelines to heart. I'll be reading all the linked presentations/articles this after lunch, too. Again: thank you for being patient and putting in the effort despite my stubbornness :) – Elias Van Ootegem Sep 25 '12 at 06:54
0

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
balafi
  • 2,143
  • 3
  • 17
  • 20
0

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

Svend
  • 7,916
  • 3
  • 30
  • 45