Blog-Archiv

Samstag, 3. Oktober 2015

JS Light Bulb Moments

JavaScript holds some oddities that always make me breathless. Things you are used to, from practising other programming languages, lead to bugs in JavaScript.

This Blog contains a number of useful try-outs that are never too late to learn about.
As preparation you can also read my passed Blog about JS gotchas.

In all of the text-areas in the following, you can click on "Run" to see the result of the example script.
If you want to alter the script, simply edit it and then press "Run" again.
If you want to know how these text-areas work, you can read my latest Blog.

In many cases the JavaScript visibility scope is the problem. Try to guess what this script yields:

Example 1:

What happens here? We define a variable alfa with a value. Then we define an object wolf with two members, first is a field alfa, second is a function getAlfa that returns the alfa-value of the wolf. But which alfa is the one of the wolf?

What is intuitively expected here: that the alfa-wolf is "the one and only" :-)

In fact, using JS, the result is "the real one", the value of the variable alfa.
Why is this? How can we make it the expected "the one and only"?

Example 2:

This yields what was expected: "the one and only".
To reference the alfa field in the wolf object, we need to write either this.alfa or wolf.alfa. JS has no implicit object scope. When you want to access the members of an object in a member function, you need to either use the name of the object, or the keyword "this". But "this" is another story. First lets try out whether "this" is safe against embeddings.

Example 3:

Seems to be stable. This yields "the follow-up" for wolfie, so wolfie has its own alfa value.

Now imagine that we, after construction of the object wolf, add a function getWolfieAlfa() to wolf that outputs wolfie's alfa-value:

Example 4:

Now wolfie's alfa-value has become "the one and only". This is the "this" story, and it tells us that this is always the caller of the function. And when you set functions to point to other functions, which is easy in JS, this does not work any more like expected. For such pitfalls you search really long. I would recommend to not use this.


But this is not the only scope-oddity. Look at following script and try to guess what sheepCount will be:

Example 5:

I use a self-executing anonymous function as encapsulation mechanism to write my sheepCount assignment. Nevertheless the alert shows "999". Why is the sheepCount variable known outside my capsule?

Because I forgot to write

var sheepCount = 999;

to make it a local variable. The var keyword makes the difference. Change the script to be var sheepCountNew = 999 with alert(sheepCountNew) and try it out, the JS interpreter will then report that sheepCountNew is not defined. Mind that if you do not change the name of the variable, the result always will be 999, because sheepCount already has become a global variable.

By forgetting the var keyword it is really easy to create lots of global variables. Thus we always should write

"use strict";

on top of any JavaScript. JS interpreters of modern browsers will consider this and deny the usage of variables that were not defined using the var keyword. Mind that variables are not fields. Fields are members of an Object defined within {}.


So far about scope and visibility of variables and fields. Now let's try out a little bit what functions are.

Example 6:

This script outputs "foo() was called" three times. Neither foo(bar1, bar2) nor foo(bar1) are called. They do not even exist. They both were overwritten by the last definition of foo(), which is the only survivor of this unintended competition for the function-name foo. This example shows that there is no function overloading in JavaScript.

If we had written it the following way, we would have seen the pitfall immediately:

    var foo = function(bar1, bar2) {
        alert("foo(bar1, bar2) received "+bar1+", "+bar2);
    };
  
    var foo = function(bar1) {
        alert("foo(bar1) received "+bar1);
    };
  
    var foo = function() {
        alert("foo() was called");
    };

Because of that reason I recommend to define functions as var. Actually this is what the JS interpreter does when it finds function definitions like in the source code above. In a strict way they are syntactically wrong.


But we are not done. JS is bursting at all seams. Have a look at "variable hoisting":

Example 7:

Here we define a function that outputs the value of a variable that seems to not yet exist, then we declare a { block } containing the variable number with value 1, and after that block we again output the value of that very variable.

Anybody would expect the output to be undefined both times. But it isn't. JS "hoists" any variable definition to the top of the function body. This is the reason why the script is not crashing due to an undefined variable usage at the first alert statement. Down in the block the variable then gets its value, and, as it was actually "hoisted" to the function top, it survives the block termination and the second alert shows us that it holds the value 1.


As last example lets look at a very common pattern to define default values, to be seen in many scripts. I am talking about the || logical operator. This is used to assign a value to a parameter when the caller of the function did not pass a parameter, or passed undefined or null. The value at right side of the || operator would be assigned to the variable only when the left side is false, undefined or null. Assuming the parameter is called ignoreCase, it reads like

let ignoreCase be ignoreCase or true.
Example 8:

A function foo() expects a boolean parameter and assigns a default value of true to it when it was not provided by the caller.

  • foo calls 1 - 3 do as expected, the value true is reported
  • call 4 leaves the parameter at its own value, because it is true, and thus the right side of || is not reached
  • call 5 then shows another embarrassing JS pitfall: the parameter is turned to true although it was given as false!

You see the problem? Instead of assigning a default we forced the parameter to be always true.

So the default mechanism with || can be used only for objects and strings, not for booleans and numbers. Evaluating a number would yield false for 0, and true for any other value, similar problem as with booleans. You can dive into this on stackoverflow if you want.

So every JS programmer clearly has in mind that for boolean and number parameters (s)he must write

    if (ignoreCase === undefined)
        ignoreCase = true;

Do you believe they all know?
Do you know that we are about to program user-interfaces in JavaScript now on a large scale?
Do you know that most efforts in software development go into user-interfaces, and that software always has been much too expensive for normal people?




Keine Kommentare: