Blog-Archiv

Montag, 6. Juni 2016

JS jQuery $(this)

One of the most mysterious things around jQuery is the expression

$(this)

especially in context of the jQuery loops

  • $.each()
  • $(cssSelector).each()

I had a closer look at that, and wrote some test code for this Blog.

jQuery is a function

jQuery is a normal JS function. It has $ as alternate name. So calling

$(".myCssClass")

is exactly the same as calling

jQuery(".myCssClass")

jQuery accepts up to two parameters, see API description.
Mind that you can pass a lot of different things as first parameter!

this is a JS constant

The JavaScript "this" keyword represents a constant pointing to the caller of the currently executed function. That means when you have an object "bar" with a function "foo" ...

var bar = {
  foo: function() {
    return this === bar;
  }
};
alert(bar.foo());

... you would see true on the alert-dialog, because the object bar is the caller of foo().
Some also say that "this" is what is left of the dot.
When you have nothing on the left side of the dot, it will be the window object, as window it is the default-context for JS interpreters running in a browser.

Mind that "this" is JavaScript, not jQuery. So the expression

$(this)

is a call to jQuery with the JS this pointer as parameter!

jQuery each()

The each() loop requires a function as parameter. jQuery ensures that you can access the looped item by the this keyword within that function. That loop-body function can have no parameter, one parameter, or two parameters. If you decide to have no parameter, you still can access the looped item by this. Declaring one parameter will let you receive the index of the item within the loop-body. The second parameter will be the item itself. (Only God and the jQuery programmers know why the item is the second and not the first parameter.)

$(".someCssClass").each(function(index, item) {
  var bareThis = this;
  var jQueryThis = $(this);
  
  isThisAllTheSame(bareThis, jQueryThis, item);
});

In this source code, jQuery would read all DOM-elements from current document that have an attribute class = "someCssClass". Then it would call the anonymous function that was passed to each() with each of those DOM-elements as second parameter.

Now the interesting question is what's the difference between the three opportunities to access the loop item:

  1. the bare JS this,
  2. the jQuery $(this),
  3. and the parameter item.

Test Page

Here is a HTML test page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>jQuery each() test</title>
    
 <script
  src="https://code.jquery.com/jquery-2.2.4.js"
  integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
  crossorigin="anonymous"></script>
</head>
     
<body>

 <div id="one" class="loop">One</div>
 <div id="two" class="loop">Two</div>
 <hr>
 <div id="output"></div>
 
 <script type="text/javascript">
  
  var outputElement = function(bareThis, jqueryThis, element) {
   // ...
  };
  
  var loopBody = function(index, element) {
    var bareThis = this;
    var jqueryThis = $(this);
    outputElement(bareThis, jqueryThis, element);
  };
  
  
  $(".loop").each(loopBody);

 </script>

</body>
</html>

Copy & paste this into some file and load that file into your browser. Currently nothing will happen, because the outputElement function is not yet implemented.

  var outputElement = function(bareThis, jqueryThis, element) {
    var output = $("#output");
    var outputText = "<b>id = "+jqueryThis.attr("id")+"</b><br>";
    outputText += "bareThis = "+bareThis+", element = "+element+", jqueryThis = "+jqueryThis+"<br>";
    outputText += "typeof bareThis = "+(typeof bareThis)+", typeof element = "+(typeof element)+", typeof jqueryThis = "+(typeof jqueryThis)+"<br>";
    outputText += "bareThis == element: "+(bareThis == element)+", jqueryThis == element: "+(jqueryThis == element)+"<br>";
    outputText += "bareThis === element: "+(bareThis === element)+", jqueryThis === element: "+(jqueryThis === element);
    output.html(output.html()+"<br><br>"+outputText);
  };

Looping DOM Elements

Having the output-function added, this is what we see now for the first element with id "one":

id = one
bareThis = [object HTMLDivElement], element = [object HTMLDivElement], jqueryThis = [object Object]
typeof bareThis = object, typeof element = object, typeof jqueryThis = object
bareThis == element: true, jqueryThis == element: false
bareThis === element: true, jqueryThis === element: false

It is never easy to get clearness about JS objects, so that output is somehow complicated. To summarize it, we see that the bareThis and element parameter are identical, it is the DOM-node, and the jQueryThis is the usual jQuery wrapper around such a DOM-node.

What we learn here is that this points to the item of the loop, not to the caller of the function.

How can this be?
There is a built-in JS function

Function.call(context, parameters)

that lets set the this pointer for the called function as first parameter. jQuery uses exactly that to call the loop-body function. So the this keyword does not really have a constant semantic, it is more a read-only variable that can point to any object the caller considered to be useful.

Looping a String Array

Here is JS code that loops over a String array.

  var array = [
   "#one",
   "#two"
  ];
  $(array).each(loopBody);

Resulting output for the first element is:

id = undefined
bareThis = #one, element = #one, jqueryThis = [object Object]
typeof bareThis = object, typeof element = string, typeof jqueryThis = object
bareThis == element: true, jqueryThis == element: false
bareThis === element: false, jqueryThis === element: false

The bareThis and element parameters are equal, but not identical. The bareThis is a String-object that was built by $(array), while element is the real String from the array.

Amazingly I could not trick jQuery to read the DOM-node with id "one": $("#one")!

What we learn is that the this pointer is not what we should use when we really want to work with the array item, because here it was built by jQuery that deep-cloned the array. But as long as identity === is not required, this will be sufficient.

jQuery Source Code

To understand parts of this Blog better, here is the original code of jQuery each(), which is executed by both variants of that function.

  var each = function( obj, callback ) {
   var length, i = 0;
 
   if ( isArrayLike( obj ) ) {
    length = obj.length;
    for ( ; i < length; i++ ) {
     if ( callback.call( obj[i], i, obj[i] ) === false ) {
      break;
     }
    }
   } else {
    for ( i in obj ) {
     if ( callback.call( obj[i], i, obj[i] ) === false ) {
      break;
     }
    }
   }
 
   return obj;
  };



Keine Kommentare: