2

I walked on something weird while doing some JS code today.

I wanted to execute an object's method if the property existed, and some other function if it didn't. Feeling a little bit fancy, I wrote something like:

var obj = {
    method: function(){
        console.log(this);
    }
}

(obj.method || some_other_function)();

This executes obj.method if it exists, and some_other_function otherwise.

But the this keyword refers to the window Object when obj.method is executed, and I have absolutely no idea why.
Note that executing (obj.method)(); gives the expected result (this referring to my object)

Obviously I don't need this syntax to make my code run, but I really wonder what's happening here.

I couldn't find any answer either here or elsewhere, the closest thing I found is this interesting post, but it doesn't cover this specific case.

Anyone knows what's happening there?

here is a fiddle showing the thing in action!

mikaël
  • 453
  • 3
  • 8
  • I would recommend reading this (long-ish but well explained) : https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/ch2.md – Maxime Peloquin Dec 15 '15 at 19:55
  • Thanks for the link! I try to read stuff, watch things, and I kind of find my way when I code, but JS definitely has some very specific features! This article seems very detailed, thanks again! – mikaël Dec 15 '15 at 20:26

3 Answers3

4

(obj.method || some_other_function)(); is, essentially, the same as:

var f = obj.method || some_other_function;
f();

You've taken the function away from the context of obj by running it through or before you call it.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • You also need to explain why `(obj.method)();` is not the same as `var f = obj.method; f();` ;) – plalx Dec 15 '15 at 19:58
  • Ouch, I might have misunderstood the ' this ' keyword... I had never played with execution context until now, and this could explain some weird other bugs I have encountered in JS o_O Thank you for this clear and concise explanation! ( that was a kind of noobish first-question on SO :) – mikaël Dec 15 '15 at 20:09
  • @plalx I guess that parenthesis without some operator don't change the expression it contains, and therefore there is no implicit intermediate variable? I admit I didn't figure it out at the beginning tho ( that's indeed why I wrote `(obj.method)();` as a comparison :) – mikaël Dec 15 '15 at 20:45
0

obj.method() is syntactic sugar for

method.call(obj)

Also known as a method invocation (which is different from invoking a function). If the property (method) is used in any other way it returns a function value. Eg. using it in a binary operation such as

(obj.method || some_other_function)()

makes it a function value and the above expression is syntactic sugar for

(obj.method || some_other_function).call(this)

this is then the current context and not obj.

(obj.method)() is still a method invocation since (expression) and expression are treated equal

Rune FS
  • 21,497
  • 7
  • 62
  • 96
  • Thanks, indeed I hadn't noticed that my `||` was creating an implicit variable, And I also didn't realise that you could turn a method into a function, (I almost only use the scope to isolate stuff, nothing more) and this problem helped me a lot! – mikaël Dec 15 '15 at 20:35
  • Awesome edit, I'm only beginning to play with all these `call` , `bind`, `apply` ... properties, and I now greatly understand the `call` ! Now I clearly know **why** the `this`keyword is different. It's the eternal trouble of high level languages, you have to learn all what happens under the hood as a counterpart of not having to reinvent everything..! – mikaël Dec 16 '15 at 10:55
0

Your confusion is very common for people learning JavaScript and stems from an incomplete understanding of this. The short answer is that your code is either executing obj.method() or some_other_function(). The context of this inside of an invoked function refers to the object that invoked that function. In a typical use case of calling the method function (simply calling obj.method()), obj invokes method and so this refers to obj in the function block.

However, the expression (obj.method || some_other_function)() doesn't appear to be invoked by any object, it's just...invoked, right? What's actually happening is that this expression is called from the global space, which, in the browser, is the window object. So you can think of your code as being executed like this:

obj : {
    method: function(){
        console.log(this);
    }
}

window.(obj.method || some_other_function)();

Both some_other_function and obj.method are invoked from the global space because they are evaluated as an expression inside of your || conditional block. That expression is actually just a property on the window object, and that is what is invoking it. I was confused about this too at first, very interesting problem and thanks to Quentin for the correct answer.

Here is a great blog post that helps clarify this in JavaScript: http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

Edited: I've updated this post to reflect the correct answer, my original answer was incorrect.

  • Thanks for your answer, @Travis, but the problem was really only about the `obj.method` in which `this` was referring to the `window` object. As @Quentin pointed out, it's because my logical OR operation sort of takes the `method` out of `obj` (see his answer) so the `this` keyword is actually the window. – mikaël Dec 15 '15 at 20:20
  • And this shows that even people (me) who think they have a good understanding of `this` still get confused :) – Travis Neufeld Dec 15 '15 at 20:38
  • I thought I did too, don't worry ;) – mikaël Dec 15 '15 at 21:15