ctodx

This Blog

News

Favorite Posts

Archives

Webinar wrap-up: Julian on JavaScript IV: Good code, bad code

After the whirlwind tour of JavaScript types, objects. arrays, and functions, it was time to take a look on how to wrap those introductory presentations into a set of good code/bad code examples. You can watch the webinar here (link coming) and download the slidedeck here, but to be honest this blog post is essentially my script for the webinar, so you might as well just read on.

Truthy vs falsy.

Dog Wastephoto © 2008 Sean | more info (via: Wylio)There are 6 falsy values, values that when coerced to a boolean value will evaluate as false:

  • false (type: boolean)
  • null (type: object)
  • undefined (type: undefined)
  • 0 (type: number)
  • NaN (type: number)
  • "" (type: string)

All other values evaluate as true when coerced.

So the condition in "if (expression)" evaluates as true only if expression is not equal to one of the six falsy values.

Note this is great when you're trying to see if a variable, property or function exists or not:

  if (myObj.someFunction) {
    ... someFunction exists so we can call it ...
  } 
  else {
    ... someFunction is either null or undefined (or 0 or NaN, etc) ...
  }

A longer example: the Object.create method for creating objects that inherit from some prototypal object:

  Object.create = Object.create || function (obj) {
    var F = function() {};
    F.prototype = obj;
    return new F();
  };

Here we’re setting Object.create to itself providing that it already exists (so, in essence it’s a do-nothing operation). If it doesn’t exist, we set it to the anonymous function that is the rest of the statement. The || (or) operator used in this context means: “evaluate the first operand as a boolean, if true (truthy) return the first operand as is; otherwise return the second operand.” It’s a standard, good, succinct way of writing this kind of test.

Here’s another example, involving a string variable. You need the color variable to have a default value if it hasn’t already been set (imagine the variable is a parameter to a function: if the caller didn’t pass anything, you want to use a default value). Don’t write this C# look-a-like:

if ((color != null) && (color != "")) { 
  color = "black"; 
}

But do this instead:

color = color || "black";

Coercion happens when you least expect it.

What are the results of these tests?

console.log(0 == "") 
console.log(false == 0) 
console.log(0 == "0") 

Weirdly, they all evaluate to true. That's because if the two sides of a double-equals comparison are not the same type, JavaScript coerces them to the same type and then does the comparison. Note though that this coercion means that '==' is not commutative. That is, you can't say in JavaScript : if a==b and b==c then a==c. Try it with the above set of tests: they demonstrate that 0 is “equal” to the empty string and it’s “equal” to the string “0”, yet the empty string does not equal “0”. This non-commutativity goes against everything we assume about computing and mathematics.

Here's another, really wacky one:

var o = { 
  toString: function() { return "42"; } 
}; 

console.log(42 == o);

That is: this object with a toString method that returns the string “42” is “equal” to the number 42. Backing away yet?

We should always use the triple-equals operator (even if it's obvious from the context that == will work as expected). Triple-equals works like this, in essence: if the two operands are not the same type, return false. Otherwise return the result of comparing the two operands. It works like C#'s == operator. It's known as the Identity Comparison operator.

Use literals for booleans.

Never ever do this:

var isCsharp = new Boolean(false);

Because this condition (that you’d naturally write) evaluates as true

if (isCsharp) { 
  // Uh, whaaat? 
}

Always use the literals true and false.

Use literals for objects and arrays.

This is the best way to spot a beginner JavaScript coder:

var color = new Object(); 
color.R = 23; 
color.G = 176; 
color.B = 0; 

Or, using an array:

var primes = new Array(); 
primes[0] = 2; 
primes[1] = 3; 
primes[2] = 5; 
primes[3] = 7; 
primes[4] = 11; 
primes[5] = 13; 
primes[‍6] = 17; 
primes[7] = 19;

You should declare objects and arrays by using object and array literals instead:

var color = { 
  R: 23; 
  G: 176; 
  B: 0; 
}; 

var primes = [2, 3, 5, 7, 11, 13, 17, 19];

Doing it this way (a) is easier to read, and (b) it's easier to include literal objects and arrays in literal objects and arrays. I’ve learned that some browsers are also optimized to create objects and arrays using the literal syntax; that is, it’s marginally slower to create them using the new keyword.

Scope: We're not in Kansas any more.

Scope in C# is introduced with blocks, that is, braces. Scope in JavaScript is introduced by functions.

Quick test: What does this print?

var f = function() { 
  var a = [2, 3, 5, 7]; 
  var foo = "outside";
  
  for (var i = 0; i < a.length; i++) { 
    var foo = a[i].toString(); 
  }

  console.log(foo); 
};

f();

Reading it with your C# hat on you’d say that the console.log call prints out “outside”, since the foo variable declared in the loop body is in a different scope to the outer foo. In JavaScript, though, the two foo declarations reference the same local variable, the one in the function. Hence the logging statement prints “7”, the last value that the one and only foo variable was set to.

How about this one? It’s essentially the same code, except that the loop body has been replaced with declaring and executing a function. In that function, a foo variable is declared.

var f = function() { 
  var a = [2, 3, 5, 7]; 
  var foo = "outside";

  for (var i = 0; i < a.length; i++) { 
    var g = function(a, i) { 
      var foo = a[i].toString(); 
    }; 
    g(a, i); 
  }

  console.log(foo); 
};

f();

This time, no contest: the code prints the word “outside”. If, however, you leave the var keyword out of the inner function, we get the same behavior as before. The foo variable isn’t declared in the inner function, so JavaScript checks the next outer function. There is a foo there, so it gets set when the function g() is called.

Semicolon insertion.

JavaScript is "forgiving" when you miss off a semicolon and will insert one where it thinks it should go. DON'T RELY ON THE INTERPRETER GETTING IT RIGHT. Also, if you forget semicolons, sometimes the error message you get back was entirely because the interpreter has invisibly added one, leading to major befuddlement.

Silly example:

var f = function() { 
  return 
  { 
    status: true; 
  }; 
};

console.log(f());

seems to declare a function that returns an object with a single property called success. However, the return statement is badly formed and JavaScript will neatly add a semicolon at the end of the return keyword. If you like, this is what actually gets executed.

var f = function() { 
  return;
  { 
    status: true; 
  }; 
};

console.log(f());

In other words, the function returns undefined. JavaScript doesn’t even raise a warning to say that this modified interpretation includes unreachable code. You should be using JSLint (see later) to check your code.

Parsing strings as numbers.

parseInt converts a string to an integer. Works pretty well, but there are a couple of gotchas:

  • it stops parsing when it gets to an invalid character and returns what it's parsed so far with no error. So parseInt("42") and parseInt("42 High Street") both return 42.
  • if the first character is 0 (zero) it assumes that the number is expressed in octal. So parseInt("07") returns 7, whereas parseInt("08") returns 0 (the character “8” is invalid in octal numbers).

Advice: always use the optional radix argument. So, parseInt("08", 10) always returns 8. If you’re prone to forgetting, write yourself a little function:

var parseDecimalInt = function (expr) { 
  return parseInt(expr, 10); 
};

The '+' operator.

...Can mean addition of numbers or concatenation of strings. Mix your types and coercion will muddy the waters. Let’s write a quick function that prints both the type of its parameter and its value.

var f = function(value) { 
  console.log(typeof value); 
  console.log(value); 
};

Now can you determine what these calls will print?

f(41 + 1); 
f(“a” + “bc”); 
f(42 + ""); 
f("42" + 1);

Answers: number 42, string “abc”, string “42”, string “421”. When one of the operands is a string, somewhere the other operands will be coerced (there’s that word again) into strings.

Note though that the unary plus operator is only for numbers, and is a handy way to convert a string to a (decimal) number.

var s = "42"; 
f(+s); 

will return the number 42. However, note that this following code produces the number NaN (and is different in behavior than parseInt):

s = "42 High Street"; 
f(+s);

The for..in statement.

for..in enumerates an object's or an array's properties (but not methods). It superficially looks like C#'s foreach statement, but beware. The problem with it is that the properties it enumerates can bleed through from the prototype object(s).

Let’s write a wrapper function that uses it:

var enumerate = function (obj) { 
  console.log("enumerating..."); 
  for (name in obj) { 
    console.log(name + ":  " + obj[name]); // prints the property name followed by its value 
  } 
  console.log("...done"); 
}; 

Now we can use this to enumerate the properties for an empty object:

var o = {}; 
enumerate(o);

The result is this:

enumerating...
...done

And now for a non-empty object:

var o = { 
  count:42, 
  success:true 
}; 
enumerate(o);

The result is this:

enumerating...
count: 42
success: true
...done

Now let's create an object that inherits from this object using the Object.create method declared above:

var p = Object.create(o); 
p.color = "white"; 
enumerate(p);

The result is this, and notice the for..in has enumerated the prototype’s properties as well as the single top-level property: 

enumerating...
color: white
count: 42
success: true
...done

Remember that the first object is a prototype so let's augment it to show that even new properties for the prototype “bleed” through:

o.name = "original"; 
enumerate(p);

Resulting in this:

enumerating...
color: white
count: 42
success: true
name: original
...done

Sometimes you only need to know the properties of that object, and not of its prototype chain. Here’s a “strict” enumeration function that uses the hasOwnProperty() method (this is inherited through the Object prototype):

var strictEnumerate = function (obj) { 
  console.log("strict enumerating..."); 
  for (name in obj) { 
    if (obj.hasOwnProperty(name)) { 
      console.log(name + ":  " + obj[name]); 
    } 
  }
  console.log("...done"); 
};

With this, we can enumerate the properties that strictly belong to each object, and that are not inherited.

strictEnumerate(o); 
strictEnumerate(p);

With arrays, for..in is simply not recommended (arrays are after all objects). Let’s write a property enumerator for arrays that iterates through the elements of the array using a standard for loop:

var arrayEnumerate = function(a) { 
  console.log("enumerating array..."); 
  for (var i = 0; i < a.length; i++) { 
    console.log(i + ":  " + a[i]); 
  } 
  console.log("...done"); 
};

Now we can use this as well as the previous enumerate function to list the properties/elements of an array:

var a = [2, 3, 5, 7]; 
enumerate(a); 
arrayEnumerate(a);

a[7] = 19; // missing out [4], [5], and [‍6]
enumerate(a); 
arrayEnumerate(a);

a.originalCount = 4; 
enumerate(a); 
arrayEnumerate(a);

That last test produces this result. (Notice in particular that for..in only enumerates defined properties.)

enumerating...
0: 2
1: 3
2: 5
3: 7
7: 19
originalCount: 4
...done
enumerating array...
0: 2
1: 3
2: 5
3: 7
4: undefined
5: undefined
6: undefined
7: 19
...done

I would recommend that you execute this code in Firebug in Forefox (or use whatever developer tools that come with your favorite browser) to see what’s going on and what gets printed.

Don't repeat work (DREW)

When you are writing browser-dependent code and are not using some library that abstracts out this difference, you may be repeating the same work over and over. Let's take a look at adding and removing handlers for DOM objects:

var addHandler = function(domObject, eventType, handler) {
  if (domObject.addEventListener) {
    domObject.addEventListener(eventType, handler, false);
  }
  else { // early IE
    var ieEventType = "on" + eventType;
    domObject.attachEvent(ieEventType, handler);
  }
};

var removeHandler = function(domObject, eventType, handler) {
  if (domObject.removeEventListener) {
    domObject.removeEventListener(eventType, handler, false);
  }
  else { // early IE
    var ieEventType = "on" + eventType;
    domObject.detachEvent(ieEventType, handler);
  }
};

In essence: if the DOM object has a method called addEventListener (or removeEventListener), use it to add the handler, otherwise assume that we’re using an old IE and massage the parameters accordingly and use attachEvent (or detachEvent).

Since the user can't suddenly switch browsers half way through an online session, we know with 100% certainty after the first time we execute the if statement how all subsequent ones will evaluate. We're repeating work unnecessarily. In this case, it’s not much work (a simple null/undefined condition test), but it can add up.

One of the best ways of avoiding this is to rewrite the functions with the correct version. Remember: functions are objects. We can assign objects to variables whenever we want to. The result is a form of lazy-loading.

var addHandler = function(domObject, eventType, handler) {
  if (domObject.addEventListener) {
    addHandler = function(domObject, eventType, handler) {
      domObject.addEventListener(eventType, handler, false);
    };
  }
  else { // early IE
    addHandler = function(domObject, eventType, handler) {
      var ieEventType = "on" + eventType;
      domObject.attachEvent(ieEventType, handler);
    };
  }
  addHandler(domObject, eventType, handler);
};

var removeHandler = function(domObject, eventType, handler) {
  if (domObject.removeEventListener) {
    removeHandler = function(domObject, eventType, handler) {
      domObject.removeEventListener(eventType, handler, false);
    };
  }
  else { // early IE
    removeHandler = function(domObject, eventType, handler) {
      var ieEventType = "on" + eventType;
      domObject.detachEvent(ieEventType, handler);
    };
  }
  removeHandler(domObject, eventType, handler);
};

Looking at the “add” code: the first time addHandler is called it checks for the presence of addEventListener. If it’s there, the code then replaces addHandler with the right code for the current browser, and then calls it. If it’s not there, addHandler is replaced with IE-specific code. From that moment on, every time we call addHandler we will only execute the code for the current browser. No more if statements. (Ditto for removeHandler.)

JavaScript is a scripting language. Now forget it.

Like all scripting languages, JavaScript allows you to construct some code as a string and then execute it from within your already executing code. There are four ways to do this: eval(), the Function constructor, setTimeout() and setInterval().

First problem with these: the code gets evaluated twice at run-time; first when your code gets evaluated, and second when the string is evaluated as code. Another case of repeating work unnecessarily. Second problem with them is that a new instance of the interpreter has to be initialized in order to evaluate the code-as-string. With modern interpreters, there might also be extra work needed to compile/optimize the code.

In practically every case though, the code-as-string can be rewritten as a normal function. It's rare that code has to be constructed and executed at run-time.

Here's an example that “ticks” by printing 10 asterisks at 1/2 second intervals:

var o = {
  count : 10,
  tick : function() {
    setTimeout("if (o.count > 0) {o.count--; console.log(\"*\"); o.tick(); }", 500);
  }
};

o.tick();

Imagine what the interpreter has to do when it compiles that string (the first parameter of the setTimeout call). It has to parse it. It has to construct the right scope object so that the new code can access the object being referenced. It has to do this every half-second. Nasty.

var p = {
  count : 10,
  tick : function() {
    setTimeout(function() {
      if (p.count > 0) {
        p.count--; 
        console.log("*"); 
        p.tick(); 
      }
    }, 500);
  }
};

p.tick();

Here I’ve just rewritten the function setTimeout will call as an anonymous function. Parsed only once, and it already has access to the correct scope, etc.

Using JSLint as a code-checker.

One of the issues C# developers run into when they write JavaScript is that there is no compiler telling you of obvious problems with your code. Yes, they may be inadvertent errors, mistyping an identifier, missing off a semicolon, etc, but it would be nice if there was a program caught them before you had to run it and try and interpret some bizarre error that resulted. If you've followed this series from the start you'll have seen this in action every time as I made a typing error in the heat of the moment.

Enter JSList. It's a tool written by Douglas Crockford that does exactly that. It will report on simple syntax errors in your code and its repeated use will help improve your code.

Published Mar 28 2011, 03:58 PM by
Filed under: , ,
Bookmark and Share

Comments

Marc Greiner (DevExpress MVP)

The javascript creator is a dangerous criminal.

March 29, 2011 4:59 AM

Julian Bucknall (DevExpress)

Marc: And generally sits in a comfy chair stroking a white cat plotting the overthrow of all the world's governments. Bwhahahaha!

Cheers, Julian

March 29, 2011 11:43 AM

Marc Greiner (DevExpress MVP)

Until I discovered javascript, I though that VB6 was the worst possible language, but I was so far from the reality.

I realy hope that the javascript inventor stays in his *comfy chair* forever and doesn't invent anything else anymore!

In any case, you deserve a BIG Thank You for making javascript more understandable to me.

March 29, 2011 12:26 PM

Christian Fiebrig

Hello.

Any chance, that the link to the video is coming? I didn't find the webinar video on the tvChannel and in the webinar list it is still pending.

June 23, 2011 7:35 AM

Daniel Shitrit

The video is not available yet.

There is no link to slides in the begining of this post.

Thanks,

June 29, 2012 2:48 AM

About Julian Bucknall (DevExpress)

Julian is the Chief Technology Officer at Developer Express. You can reach him directly at julianb@devexpress.com. You can also follow him on Twitter with the ID JMBucknall.
LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners