Blog-Archiv

Freitag, 19. September 2014

Omigosh, JavaScript!

This is the Blog I shied away from many times, because JavaScript really divides the world.

The late Nineties. People talked about the end of programming languages. Big computer companies predicted that everything will be graphical, the UNIX commandline shell was said to be outdated. Some even told us that in future we will build software applications by drag and drop, not by writing source code.

What a contradiction: what was expected to be the end time of programming languages actually was the start time of a language called JavaScript. It was used in internet browsers to do things that were hard to specify using HTML tags. First it was called LiveScript and then renamed to JavaScript, although it has nothing to do with the Java language. It is one of the most error-prone languages I know.

Nowdays applications are still created using programming languages. JavaScript (JS) survived, even got standardized, now called ECMA-Script (European Computer Manufacturers Association). It is the only language that all browsers support. It is going to be used as programming language for client-side applications running in browsers, which also includes an MVC-implementation (Model-View-Controller).
Still it is believed that JavaScript has something to do with Java, but it still has not :-) - except that Java includes a JS scripting engine meanwhile, maybe because that believe was so strong?

Differences


Java

is a language that is compiled from .java files into .class files which then are interpreted by a Java virtual machine (JVM) that is available for nearly every platform. So you can let run your .class file on LINUX or WINDOWS, it will work without recompilation.

You can develop in Java by downloading the free Java developer kit (JDK), and you can run your Java application using a Java runtime environment (JRE, part of the JDK). Since the beginning Java provided a platform-dependent graphical user interface (GUI) called AWT, since 1999 also a platform independent one called Swing, which is now going to be replaced by JavaFX (running also on mobiles that support Java).
Java was available also in browsers (-> applets), but 2013 it was banned by Apple because a binary language seems to be too much a security hazard for an internet browser (Apple banned applets, what a coincidence:-).

Java is a very simple language, lacking lots of cryptography existing in C and C++. It is fully object-oriented, but without multiple inheritance on implementation level. It does not provide a preprocessor like C, because such obscures source code.
This is the reason why it is so popular: you can read that source code (and sometimes even understand it :-). Java is strongly typed, meaning you can implement big projects using Java.

JavaScript

is a language that is interpreted. The interpreter runs within the internet browser and reads sources from either a HTML page's script tags (JavaScript embedded in HTML) or its referenced .js files. JavaScript was made to manipulate an HTML document, it makes only little sense without the HTML document object model (DOM). Some people use it as templating-language on server side, this is where the JavaScript embedded in Java comes into play. But this is just "fashion", there are better templating languages than JavaScript.

You can develop JavaScript by writing some HTML, including a <script> tag containing some JavaScript, and then loading that HTML page into the browser of your choice, using a file-URL like file:///home/me/mypage.html. When your script does not work, try to find the browser's developer tools, look at the console, or set a JS debugger breakpoint into your source and reload the page.

JavaScript also lacks a lot of C/C++ cryptography, but reading JavaScript source is not so easy due to the many techniques that have evolved around this very flexible language. It does not support typing. A JS variable can be a String, and in the next moment it is a number. JavaScript pretends to be object-oriented, but it isn't (in common sense). And it is not a functional language, even if you see the word "function" everywhere in source code.

The JS Problem(s)

So why do I say that this is a complicated and error-prone language?
No single and easy answer. I start with the beginner's gotchas here, and add other posts where I will continue to moan :-)
I will mix in my best practice proposals and summarize and review them at end.


Scope


Scope is the space where a variable or function is visible, callable and usable, mostly marked by curly braces. When you have
{      // scope 1
  int i = 1;
  {    // scope 2
    int k = 2;
  }
}
you expect i to die when scope 1 ends, and k to die when scope 2 ends. Variable i is known in both scopes, k only in scope 2.

Not so sure in JavaScript.
Try following example and look at the console output:

function foo() {
  console.log("within foo() we 1st have i = "+(typeof i !== "undefined" ? i : "[undefined!]"));
  console.log("within foo() we 1st have k = "+(typeof k !== "undefined" ? k : "[undefined!]"));
  console.log("within foo() we 1st have m = "+(typeof m !== "undefined" ? m : "[undefined!]"));
  console.log("within foo() we 1st have o = "+(typeof o !== "undefined" ? o : "[undefined!]"));
 
  i = 22; // goes to global scope due to missing "var"
  var k = 33; // function-local scope due to "var"
  var m = 44; // function-local scope, shadowing the global m
  o = 88; // uses to global scope o due to missing "var"
 
  console.log("within foo() we 2nd have i = "+(typeof i !== "undefined" ? i : "[undefined!]"));
  console.log("within foo() we 2nd have k = "+(typeof k !== "undefined" ? k : "[undefined!]"));
  console.log("within foo() we 2nd have m = "+(typeof m !== "undefined" ? m : "[undefined!]"));
  console.log("within foo() we 2nd have o = "+(typeof o !== "undefined" ? o : "[undefined!]"));
}

var m = -4; // is known in all functions here, at any nesting depth
o = -8; // is known in all functions here, at any nesting depth

console.log("outside foo() we 1st have i = "+(typeof i !== "undefined" ? i : "[undefined!]"));
console.log("outside foo() we 1st have k = "+(typeof k !== "undefined" ? k : "[undefined!]"));
console.log("outside foo() we 1st have m = "+(typeof m !== "undefined" ? m : "[undefined!]"));
console.log("outside foo() we 1st have o = "+(typeof o !== "undefined" ? o : "[undefined!]"));

foo();

console.log("outside foo() we 2nd have i = "+(typeof i !== "undefined" ? i : "[undefined!]"));
console.log("outside foo() we 2nd have k = "+(typeof k !== "undefined" ? k : "[undefined!]"));
console.log("outside foo() we 2nd have m = "+(typeof m !== "undefined" ? m : "[undefined!]"));
console.log("outside foo() we 2nd have o = "+(typeof o !== "undefined" ? o : "[undefined!]"));

I can not even start to present my scope example without having problems: JavaScript does not always provide the console.log() function (meaning the browser vendors do not always).
So if you want to test the source code and your browser has no console.log(), you need to output somehow into the HTML page ...

Here is the output:

outside foo() we 1st have i = [undefined!]
outside foo() we 1st have k = [undefined!]
outside foo() we 1st have m = -4
outside foo() we 1st have o = -8
within foo() we 1st have i = [undefined!]
within foo() we 1st have k = [undefined!]
within foo() we 1st have m = [undefined!]
within foo() we 1st have o = -8
within foo() we 2nd have i = 22
within foo() we 2nd have k = 33
within foo() we 2nd have m = 44
within foo() we 2nd have o = 88
outside foo() we 2nd have i = 22
outside foo() we 2nd have k = [undefined!]
outside foo() we 2nd have m = -4
outside foo() we 2nd have o = 88

As you can see, scoping is only optional in JavaScript, using the var keyword. The default for any variable you define is the global scope!

Now this is the very old problem of global variables. Nobody knows why they have a wrong value at a certain time. Because anybody can use them. Global variables are one of the oldest error sources in software applications.

Best practice: Always use var when defining variables.


Scoping anomalies are not over yet. The interpreter hoists every variable within a function (no matter at what block depth) up to the top level block starting below the function declaration. If you have several different var i = 0; statements in that function, they all will be in the same variable.

function variableHoisting(param)  {
  console.log("BEGIN: number = "+number);
  if (param)  {
    var number = 1;
  }
  console.log("END: number = "+number);
}

variableHoisting("param");

This code yields:

BEGIN: number = undefined
END: number = 1

When the variable "number" would have been undefined on the first statement of the function, JavaScript would have thrown an error. But as it hoisted the var-statement to the top of the function block, no error occurred, just the value was undefined there.

This is the reason why some JavaScript Gurus recommend to put all variable definitions to the top of the function. (Because this is what the interpreter will do anyway, thus it reflects reality better.)
What in turn makes refactoring of big functions quite difficult.


Another thing is the scope within Objects. Imagine objects nested into each other, like the following:

var outerObject = {
  x: 1,
  outerPrint: function() {
    console.log("this.x = "+this.x+", this.innerObject.y = "+this.innerObject.y);
  },
  innerObject: {
    y: 3,
    innerPrint: function() {
      console.log("this.x = "+this.x+", this.y = "+this.y);
      console.log("outerObject.x = "+outerObject.x+", outerObject.innerObject.y = "+outerObject.innerObject.y);
    }
  }
};

outerObject.outerPrint();
outerObject.innerObject.innerPrint();

Mind that functions nested into an Object must use "this" (or the full namespace "outerObject.innerObject") to access sibling properties in the same Object.
Output is:

this.x = 1, this.innerObject.y = 3
this.x = undefined, this.y = 3
outerObject.x = 1, outerObject.innerObject.y = 3

Child properties are visible from the parent scope, but the parent properties are not visible from child, except when addressing them in an "absolute" way via the global namespace.


Undefined and null

Most programming languages limit themselves to just a null keyword for undefined values. Not so JavaScript.
Here you have undefined and null. Whatever sense this distinction makes.

  • When a variable with name xyz was never defined by the programmer, but used (e.g. passed as function parameter), JavaScript regards xyz to be undefined, and it throws an error in this case.
  • When you call a function without parameters, and that function declares parameters, the values of these parameters will be undefined. No error is thrown in this case!
  • When you access an array and your index is out of bounds of that array, no error happens, you get back undefined.
  • When you declare a variable var x; but you don't assign it a value, its value will be undefined (not null).

Why do we need null then? I do not know.
The only answer I found is that you can use this to assign default values to parameters when their values are undefined, letting pass null as valid value. But that does not convince me at all, I could also regard null as undefined and assign defaults then.

Care has to be taken when talking about "undefined variables". What is undefined? The variable? Or its value? In JavaScript all seem to mingle.
For example this fails with a runtime error:

console.log(xyz);  // assume xyz was never declared as variable

while this works without error and yields object.xyz=undefined:

var object = {};
console.log("object.xyz="+object.xyz);

The only way to get around the runtime error on undefined free variables is a typeof expression:

if (typeof xyz !== "undefined")
  console.log(xyz)

For the second case, where you test the member of an object (and no runtime error is thrown), you can omit the typeof expression and replace it by a simple and good readable condition:

if ( ! object.xyz )
  object.xyz = 3;

This also works in functions when assigning default values for undefined parameters (no typeof expression is needed here!):

function foobar(foo, bar) {
  if ( ! foo )
    foo = "foo default";
  if ( ! bar )
    bar = "bar default";
  ....
}

Best practice: Test for undefined using a simple if ( ! x ) x = ...;

Don't care about free undefined variables. Use the typeof expression only in exceptional cases. Readability of source code is most important.
There is a way to get around the problem with free undefined variables, the "use strict"; statement on top of the .js file. This would instruct the JavaScript interpreter to NOT tolerate the usage of free undefined variables. Not all browsers support strict mode, but using a strict-supporting browser during development will uncover mistakes right from the start.

"use strict";  // best on top of any .js file
// JavaScript code goes here ...

Best practice: Write "use strict"; on top of any module.

The strict mode additionally checks for a lot of other pitfalls.

And for the additional complexity with null:

Best practice: Do not use null at all, use undefined instead.

Reason is: "undefined" creates enough hard-to-read code, do not mix in "null" handling additionally. There will be no use cases where you need to distinguish between them. And when you really want to use it, take care to not expose it to other modules.


For clarity, a final example about "undefined".
The following causes an error:

if ( ! z )   // assume z is not a parameter and was not defined anywhere on this HTML page
  console.log("z is false");

Also this causes an error:

z;
if ( ! z )
 console.log("z is false");

But this DOES NOT cause an error:

var z;
if ( ! z )
 console.log("z is false");



Keine Kommentare: