2

See the jsfiddle.

When I run the code below:

  var divs = $('div');

  // The first three methods of showing a div work
  divs.eq(0).show();
  (divs.eq(1).show)();
  var f = function() {
    divs.eq(2).show();
  }
  f();

  // But this doesn't. Why?
  var g = divs.eq(3).show;
  g();

The last method doesn't show the div, and there's no error in the console. I want to use it because I want to store the function concisely without creating an anonymous function block. This is what I would normally do in Python. I can't understand what goes wrong here.

EDIT: the comments aren't really helping me understand the problem. How did this get lost? What did it change to and why? Why doesn't that happen with (divs.eq(1).show)();?

Alex Hall
  • 31,431
  • 4
  • 39
  • 71
  • That's an interesting question! – Robert Moskal May 14 '16 at 19:20
  • 5
    `.show()` depends on the value of `this`, which changes when you separate accessing the method and calling it into individual steps. [Preserving a reference to “this” in JavaScript prototype functions](http://stackoverflow.com/questions/2025789/preserving-a-reference-to-this-in-javascript-prototype-functions) and [How does the “this” keyword work?](http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Jonathan Lonowski May 14 '16 at 19:24
  • 2
    result is a function with no context – charlietfl May 14 '16 at 19:24
  • Because the execution context has changed and the `this` no longer points to your `div` – haim770 May 14 '16 at 19:28
  • @JonathanLonowski why doesn't that happen with `(divs.eq(1).show)();`? Wrapping in parentheses seems like it should have the same effect as storing in a variable. They're both just creating new expressions. – Alex Hall May 14 '16 at 19:33
  • Frankly I don't understand why you need to store reference to `.show` method. It's more convenient/usable to store **context** (reference to element), then invoke method within it. – hindmost May 14 '16 at 19:35
  • @hindmost I'm passing the function as an argument to another function. – Alex Hall May 14 '16 at 19:36
  • @AlexHall The grouping operator doesn't seem to alone impact the engine's ability to track the context object for the "method" call. However, involving another operator, such as the comma operator, will impact it – `(null, divs.eq(3).show)()`. – Jonathan Lonowski May 14 '16 at 19:40
  • @AlexHall it seems unlikely that the `(.....show)()` would actually work – Alnitak May 14 '16 at 19:40
  • @Alnitak but it does, it's in the fiddle. I got it wrong originally but I edited. – Alex Hall May 14 '16 at 19:41
  • 1
    _I'm passing the function as an argument to another function._ That's not just a _function_, that's a **method** with context which is lost (as it has been wrote many times above) when you store that method in a variable – hindmost May 14 '16 at 19:41
  • @hindmost right, which is what I'm learning, but you said you didn't understand why I want to do this. – Alex Hall May 14 '16 at 19:43
  • @JonathanLonowski ok, that one gets a 'WAT?!' from me. I would have read the braces as creating an unbound function reference, without context. – Alnitak May 14 '16 at 19:43
  • 1
    @Alnitak It seems to be due to [the operator not using `GetValue()` itself](http://www.ecma-international.org/ecma-262/6.0/#sec-grouping-operator-runtime-semantics-evaluation). The spec calls out `delete` and `typeof` as reasons for that (I'm assuming to avoid breaking `delete (obj.foo);`). – Jonathan Lonowski May 14 '16 at 19:51

3 Answers3

3

As already explained in the comments, it doesn't work because show() depends on the value of this.

The value of this depends on how a function is called:

  • When called as a "normal" function (foo()), this will refer to the global object or undefined (when in strict mode).
  • When called as object method (obj.foo()), this refers to the object (obj).

Understanding how this works in JS is crucial. See:

Community
  • 1
  • 1
Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • Why? Why not preserve the value of `this`? I could get the global object or `undefined` easily at any time. – Alex Hall May 14 '16 at 20:12
  • 1
    @Alex: Because functions are first class objects. They don't belong to anything. Consider the following: `var foo = {bar: function() {}}; var foo2 = {bar: foo.bar};`. When I call `foo2.bar()`, what should `this` refer to? It's simply how JavaScript was designed and what makes it so flexible. Python is different because class methods are automatically bound to instances. – Felix Kling May 14 '16 at 20:14
  • 1
    E.g. in Python if you had two instances of the same class and did `inst.method == inst2.method`, you would get `false`, in JavaScript you'd get `true` (assuming the methods have been added to the prototype). Python autobinds methods to new instances (which effectively creates a new function for each instance), JavaScript doesn't. – Felix Kling May 14 '16 at 20:20
0

When you call g(), the value of this is not set to divs.eq(3). Here's one way to fix it.

divs.eq(0).show();
(divs.eq(1).show)();
var f = function() {
  divs.eq(2).show();
}
f();

var div3 = divs.eq(3);
var g = div3.show;
g.call(div3);
cambunctious
  • 4,860
  • 3
  • 22
  • 35
0

divs.eq(3).show is this function code (check it out by console.log(g) ):

(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(mb(b,!0),a,d,e)}

That this is what people have been refering to.

The function g needs to apply to something. That something, in the first case was a jQuery element (the div). In the second case though, it's the #document element.

rollingBalls
  • 1,583
  • 1
  • 11
  • 21
  • Why the document element? What would the purpose of that be? – Alex Hall May 14 '16 at 20:10
  • Originally, the `g` function was part of the jQuery object. You "took a copy of the function out of the element" by making the reference. Since now `g` belongs to the document (remember that `document.ready` context), it applies there. – rollingBalls May 14 '16 at 20:12
  • *"it's the #document element."* - Isn't it `window`? – nnnnnn May 14 '16 at 20:13
  • @nnnnnn No, do a console.log(this) on the fiddle to verify. – rollingBalls May 14 '16 at 20:15
  • 1
    Let me rephrase that more assertively: `this` *is* `window`. You are confusing the value of `this` within the ready handler (`document`) with the value of `this` within the `show` method as referenced by `g` (`window`). If you want to check using `console.log(this)` you have to add that inside `show()`. – nnnnnn May 14 '16 at 20:23
  • (Or in strict mode it should be `undefined`.) – nnnnnn May 14 '16 at 20:30