Blog-Archiv

Samstag, 20. September 2014

JS and the Forgotten Types

JavaScript is like a laptop that has USB plugs only.
You like it: the software will self-detect which device you plug in, but ... where to plug in the power cable? Because undoubtedly there will be no software detection without power ...

I am sure some day there will be a laptop like this, expecting power from every cable before it is switched on. Nevertheless I always liked the fact that I can not plug the scanner cable into the printer socket because they had different plugs. Different types of plugs. Looking from outside you could be sure that the plug connects to the correct hardware interface inside.

Typing is important. Big projects are mostly implemented using strongly typed programming languages. The compiler then checks that the scanner cable is plugged into the scanner socket, and the printer cable is plugged into the printer socket. That everything is done as intended by the developer. A developer expresses herself by types. When some code passes a parameter of type "Machine" to some other code that expects "Human", the compiler will detect and report this.

As alternative you can use duck typing: if the "Human" has a switch with the label "On/Off", it surely can be used as "Machine", so why not decide this at runtime and then use the "Human" as "Machine"?

On this side is JavaScript. In contrast to Java it provides no type checks at all.

The Types of JavaScript


It is not that JavaScript has no data types. It has

  • String
  • Number
  • Boolean
  • Array
  • Object

but it is recommended to not use them (unnecessary, slow).
Instead you use

  • var string = "me";
  • var number = 1;
  • var boolean = true; // boolean is not a keyword!
  • var array = [];
  • var object = {};

Best practice: Do not use new with predefined data types.

Exception is

  • var now = new Date();

Mention that the definition of a variable does not allow to declare a data-type for that variable. The same is for parameters of a function. So you never know what data-type the parameter you are receiving represents.

I want to look into the concept JavaScript has for arrays, maps and objects, and if the common sense about such terms is true also in the JavaScript world. Besides I want to look at the for-in loop, and why it is regarded to be a hazard.

Arrays and Maps

var map = {
 zero: 0,
 one: 1
};

console.log("map.zero = "+map.zero);
console.log("map['one'] = "+map["one"]);
// yields:
// map.zero = 0
// map['one'] = 1

Actually I allocated an object in the code above. But as it turns out you can use an object like a map. Accessing map elements is possible via variable.property or via variable["property"], this seems to be the same in JavaScript. Here I see no difference between an object and a map.
But wait, maybe a map is an array?

var array = [];
array["zero"] = 0;
array["one"] = 1;
array[-1] = -1;
console.log("array.zero = "+array.zero);
console.log("array['one'] = "+array["one"]);
console.log("array[-1] = "+array[-1]);

// yields:
// array.zero = 0
// array['one'] = 1
// array[-1] = -1

Ok, now what is it, is an array a map or a map an array. Obviously they are accessed in exactly the same way, and both have the capability to store key/value tuples. So why make a difference between them?

var map = {};
map["zero"] = 0;
map["one"] = -1;
map[-1] = -1;
console.log("map.zero = "+map.zero);
console.log("map['one'] = "+map["one"]);
console.log("map[-1] = "+map[-1]);

// yields:
// map.zero = 0
// map['one'] = 1
// map[-1] = -1

Moreover the array is indexable via negative indexes (-1), which is not imaginable in other languages. It turns out that a JavaScript array is a map where the key is the array-index and the value is the array-element. And even this is possible in JavaScript (numbers as object-properties):

var arraymap = {
 0: "zero",
 1: "one"
};
console.log("arraymap[0] = "+arraymap[0]);
console.log("arraymap[1] = "+arraymap[1]);

// yields:
// arraymap[0] = zero
// arraymap[1] = one

Seems there is no difference between arrays, objects and maps.

But now lets look at loops. Maybe this explains the difference.

Loops

var array = [];
array["zero"] = 0;
array["one"] = 1;

var map = {};
map["zero"] = 0;
map["one"] = 1;

First lets try out the good old C-style for-loop (where all that i, j, k mistakes happened :-):

console.log("array.length = "+array.length);
var i;
for (i = 0; i < array.length; i++)
 console.log("array["+i+"] = "+array[i]);

console.log("map.length = "+map.length);
for (i = 0; i < map.length; i++)
 console.log("map["+i+"] = "+map[i]);

// yields:
// array.length = 0
// map.length = undefined

So now I am stunned. No length, although there are obviously elements in both the array and map? You can not loop either of them?

Best practice: Do not use arrays as maps, their length would be zero then.

Mind that JavaScript throws no error when executing i < map.length, although there is no length property in map!

For objects without length JavaScript has another kind of loop, the for-in loop:

var key;
for (key in array)
 console.log("array["+key+"] = "+array[key]);

for (key in map)
 console.log("map["+key+"] = "+map[key]);

// yields:
// array[zero] = 0
// array[one] = 1
// map[zero] = 0
// map[one] = 1

But now I want to know how arrays can be iterated with the C-style for-loop.

var array = [
  "zero",
  "one"
];
array.push("minus one");

console.log("array.length = "+array.length);
for (var i = 0; i < array.length; i++)
 console.log("array["+i+"] = "+array[i]);

// yields:
// array.length = 3
// array[0] = zero
// array[1] = one
// array[2] = minus one

So finally that array can be like an array is in other programming languages.

Summary:

  • a JavaScript object can be used like a map, a map is an object
  • a JavaScript array is an object
  • thus an array can be (ab)used as map
  • to use an array correctly, elements should be added either at construction or by array.push()
JavaScript does not feel like a language, it is more a language kit. You can do things in many different ways.
What in turn is not very beneficial for a common sense among programmers. They all tend to have their own JavaScript - it divides the world.

The for-in loop hazard

When you program a for-in loop and a tool like jshint checks your source code, you will get a warning when you did not do it in the right way. The warning sounds:

The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.

What does that mean?
It means that the for-in loop enumerates properties of an object, and could find properties you do not want to be in that loop. These unwanted properties can come from a prototype manipulation.
The prototype is an object property that can be used as a reference to a super-class object. It was made to facilitate inheritance.
Now what that means is another story, fact is that every JS code can put properties into the Object prototype, so that every new object allocated after will have that property.

Object.prototype.myObjectProperty = -666;

var map = {};
map["zero"] = 0;
map["one"] = 1;

for (var key in map)
 console.log("map["+key+"] = "+map[key]);

// yields:
// map[zero] = 0
// map[one] = 1
// map[myObjectProperty] = -666

To avoid this you must wrap the loop body into an if-condition:

for (var key in map)
 if (map.hasOwnProperty(key))
  console.log("map["+key+"] = "+map[key]);

The Object-function hasOwnProperty() checks whether a property comes from the prototype or not. That way you can always iterate safely.

Best practice: When using a for-in loop, wrap the loop body into a hasOwnProperty() condition.

jQuery

Surely we do not want to write such technical code. Thanks god there is jQuery. The jQuery JavaScript library does for browsers what Java does for operating sytems: you can write browser-independent JS code with jQuery.
In jQuery, your loop for all of maps or arrays or objects would always look like one of the following (1st form is instance call, 2nd is static call):

$("li").each(function(index) {
  console.log(index+": "+$(this).text());
});
$.each(map, function(key, value ) {
  console.log(key+": "+value);
});

Best practice: Use jQuery wherever you can, it abstracts the browser.

By the way: '$' is a valid identifier character in JavaScript, like it it in Java. It can even be at the start of a variable or function name, and jQuery chose $ to be an alias name of the module. So when you write $.each(...), you could also write jQuery.each(...).

Best practice: Leave the $ to jQuery, for better readability do not use it in identifiers.




Keine Kommentare: