JavaScript Warning Words

When I see any of the following words in JavaScript, I worry things could be written much better some other way.


  case
  constructor
  default
  eval
  instanceof
  navigator
  new
  prototype
  stopPropagation
  switch
  this
  typeof
  void
  with

I have used new, this and prototype many times but use them less as time goes by and think eliminating them might be beneficial. new isn't completely avoidable to instantiate certain host objects. Eliminating this and prototype is a goal I've been working towards for quite a while. When I first started programming JavaScript I would never had thought that eventually I'd want to avoid these words. Now each time I learn a way to use closures instead it seems my code improves notably.

switch statements are almost always a sign that objects should have some function-valued properties to implement polymorphism.

There are a few great uses of eval in library-level code but each use should be considered very carefully for performance trade-offs.

I'm not keen on instanceof or typeof because they are in the direction of typed languages and a downward spiral into complexity; however, these two words seem unavoidable for feature detection portions of browser-destined JavaScript.

navigator makes my skin crawl. Using navigator is like relying on types in a typed language where the type system is so broken that the types are about equally likely to be lies as truth.

constructor is another broken attempt at a type system.

I've never used with. It has too many tricky subtleties and doesn't make for readable and maintainable code.

void never even crosses my mind.

stopPropagation breaks the ability of listeners related to some other task to do their work. Any one particular handler should not have this much control.

I don't consider any of these words completely off limits. If a situation arises where a word really is the best or only way to accomplish a task then I'll use it. These words are my warning list that the train may be going off the tracks.

Update I've written a follow-up article about programming without this.

Comments

Have something to write? Comment on this article.

Andrew Hedges June 13, 2008

I don't have a problem with "this" per se, but I do with the lack of consistency of its implementation.

I used "with" for a little while to see how it would go in real work. I've stopped now. Little benefit at the cost of (like you say) decreased readability.

I rarely use "switch/case" anymore, but I like knowing it's around. Could you post an example showing how you would change a switch statement into "function-valued properties"?

I'm curious, too, about your disdain for "prototype". Isn't that a basic feature of the language? I have to admit, I don't use it much, but again I'd be interested to see an example of using a closure to eliminate the use of prototype.

Thanks, Peter!

steve June 13, 2008

"this" is totally fine, and part of OOP. "with" can get you in trouble, so fine, drop that... "typeof" is very handy when determining what you were passed, or detecting if something is properly supported.

the only thing that really scares me is when I see "document.all" it indicates that the code is either old, bad, or in some rare cases, a great part of a method override to fix an IE bug (e.g. document.getElementById() fixes)

I'd worry more about forms with field names like "type", "length", "value", "submit", "reset", "name", "action", and "method"... each of which is totally fine, unless the person writing JavaScript for the page came from the MS school of coding and is badly referencing objects...

//bad MS coding style...
var newRef = myFormObject[fieldName];

which gets real fun when the fieldName is "reset" or "submit" etc.

Peter Michaux June 13, 2008

Andrew,

I feel like I have some real homework to do to answer your questions!

First is an example of drawing a heterogeneous list of shapes using a case statement. This is the type of example that makes me think "yuck" when I think about case.

(All examples written for Firefox as cross-browser isn't the focus here...thankfully.)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <title>Case Drawing</title>
  <script type="text/javascript">

    // Note returned object can be manipulated
    // i.e. no encapsulation
    var makeCircle = function(x, y, r) {
      return {type:'circle', x:x, y:y, r:r};
    };
    var circleDraw = function(circle, ctx) {
      ctx.beginPath();
      ctx.fillStyle = "rgb(0,0,200)";
      ctx.arc(circle.x, circle.y, circle.r,
              0, 2*Math.PI, false);
      ctx.fill()
    };

    var makeRectangle = function(x, y, w, h) {
      return {type:'rectangle', x:x, y:y, w:w, h:h};
    };
    var rectangleDraw = function(rectangle, ctx) {
      ctx.beginPath();
      ctx.fillStyle = "rgb(200,0,0)";
      ctx.fillRect(rectangle.x, rectangle.y,
                   rectangle.w, rectangle.h);
    };

    // A heterogeneous list of shapes
    var shapes = 
      [makeCircle(60, 20, 10),
       makeRectangle(30, 10, 20, 40)];

    window.addEventListener('load', function() {
      var ctx =
        document.getElementById('canvasEl').getContext('2d');
      for (var i=0, ilen=shapes.length; i<ilen; i++) {
        var shape = shapes[i];
        // need to use "type" property to know which draw
        // function to call.
        //
        // Note if a new type of shape is created and used
        // in the "shapes" array then this code needs to be
        // touched to accomdate that new shape.
        switch(shape.type) {
          case 'circle':
            circleDraw(shape, ctx);
            break;
          case 'rectangle':
            rectangleDraw(shape, ctx);
            break;
          default:
            throw 'what is it?';
        }
      }
    }, false);

  </script>
</head>
<body>
  <canvas id="canvasEl" width="500" height="500"></canvas>
</body>
</html>

A few months ago I was reading books on writing interpreters in C. In most examples I saw, the parser would build a tree of typed nodes. A node might be an if statement, for example. When the interpreter walked the tree, it had a case statement that looked at the type of the node and then decided which function to call to execute the node. It was very much like the above browser-scripting example of drawing some shapes where the shapes array is like the tree.

The problem is noted in the above code's onload function (i.e. the "interpreter" of the shapes array): adding more shapes should not require touching the interpreter code.

Even in C it is possible to have structs with function pointers in a vtable member and C OOP is actually quite capable. OO C is Passable

The second example solution shows something that can be done in JavaScript but not so many other languages. This solution doesn't provide encapsulation and relies on naming conventions that can be difficult to use with existing code. Sometimes using this system requires capitalizing certain letters when calling the draw function if the naming convention is different.

(I'm including this so someone doesn't feel the need to mention that the third solution I give further along is not the only non-case solution. There are plenty of other solutions as well.)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <title>Dynamic Name Drawing</title>
  <script type="text/javascript">

    // Note returned object can be manipulated
    var makeCircle = function(x, y, r) {
      return {type:'circle', x:x, y:y, r:r};
    };
    var circleDraw = function(circle, ctx) {
      ctx.beginPath();
      ctx.fillStyle = "rgb(0,0,200)";
      ctx.arc(circle.x, circle.y, circle.r,
              0, 2*Math.PI, false);
      ctx.fill()
    };

    var makeRectangle = function(x, y, w, h) {
      return {type:'rectangle', x:x, y:y, w:w, h:h};
    };
    var rectangleDraw = function(rectangle, ctx) {
      ctx.beginPath();
      ctx.fillStyle = "rgb(200,0,0)";
      ctx.fillRect(rectangle.x, rectangle.y,
                   rectangle.w, rectangle.h);
    };

    // A heterogeneous list of shapes
    var shapes = 
      [makeCircle(60, 20, 10),
       makeRectangle(30, 10, 20, 40)];

    var global = this;

    window.addEventListener('load', function() {
      var ctx =
        document.getElementById('canvasEl').getContext('2d');
      for (var i=0, ilen=shapes.length; i<ilen; i++) {
        var shape = shapes[i];
        // need to use "type" property to know which draw
        // function to call.
        //
        // If new type of shape is added don't need too touch
        // this code. If good naming convention for the draw
        // functions makes this is possible. Need an explicitely
        // named drawing function for each type of shape even if
        // two shapes could share the same drawing function.
        global[shape.type+'Draw'](shape, ctx);
      }
    }, false);

  </script>
</head>
<body>
  <canvas id="canvasEl" width="500" height="500"></canvas>
</body>
</html>

The third solution below is what I was alluding to in the original article. This has polymorphism and encapsulation which are two important of the many features of various object-oriented systems. In fact, this example displays all the features that Dr. Allan Kay used in his specification of "OOP":

"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them."

and JavaScript! :-)

Note that this solution is object-oriented without using any of new, this, and prototype. Those three words are more tied to inheritance than to encapsulation and polymorphism.

This solution is how I would first approach the drawing problem if the design requirements would allow for it.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <title>Object-Oriented Drawing</title>
  <script type="text/javascript">

    // Note complete encapsulation.
    // The returned object has a function-valued
    // "draw" property.
    var makeCircle = function(x, y, r) {
      return {
        draw: function(ctx) {
          ctx.beginPath();
          ctx.fillStyle = "rgb(0,0,200)";
          ctx.arc(x, y, r,
                  0, 2*Math.PI, false);
          ctx.fill();
        }
      }
    };

    var makeRectangle = function(x, y, w, h) {
      return {
        draw: function(ctx) {
          ctx.beginPath();
          ctx.fillStyle = "rgb(200,0,0)";
          ctx.fillRect(x, y, w, h);
        }
      }
    };

    // A heterogeneous list of shapes
    var shapes = 
      [makeCircle(60, 20, 10),
       makeRectangle(30, 10, 20, 40)];

    window.addEventListener('load', function() {
      var ctx =
        document.getElementById('canvasEl').getContext('2d');
      shapes.forEach(function(s) {
        // Note polymorphic behavior. Treating each
        // shape as implementing some "drawable" interface.
        // If new type of shape is added don't need to touch
        // this code.
        s.draw(ctx);
      });
    }, false);

  </script>
</head>
<body>
  <canvas id="canvasEl" width="500" height="500"></canvas>
</body>
</html>

This last example also shows the use of a closure to eliminate the need for new, this, and prototype when creating a circle or rectangle object. The third example above has encapsulation for each shape; however, the following code does not have encapsulation.

var Circle = function(x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
};

Circle.prototype.draw = function(ctx) {
  ctx.beginPath();
  ctx.fillStyle = "rgb(0,0,200)";
  ctx.arc(this.x, this.y, this.r,
          0, 2*Math.PI, false);
  ctx.fill()
};

To be truthful, enforced encapsulation isn't that important to me when working alone. I know I can ignore the public x, y, and r properties of the Circle instances that would be generated with new Circle(1, 2, 3);

It is true that this third example to the drawing problem may use more memory space than using prototype. This is because in the third example the draw function for a particular shape must be created for each object in order to capture the closure variables. The JavaScript implementation may be able to share the actual abstract syntax tree of the function and only have symbol table for each object. I'm not sure how that works but if necessary this could be written as the following to use less space, I believe.

var makeCircle = (function() {

  var draw = function(ctx, x, y, r) {
    ctx.beginPath();
    ctx.fillStyle = "rgb(0,0,200)";
    ctx.arc(x, y, r,
            0, 2*Math.PI, false);
    ctx.fill();
  };

  return function(x, y, r) {
    return {
      draw: function(ctx) {
        draw(ctx, x, y, r);
      }
    }
  };

})();

I wouldn't go so far to say I have "disdain" for prototype but I'm not particularly fond of it these days.

I don't think that prototype is as basic a feature of JavaScript as function is, for example. A large program can be written without using prototype but it is essential to have function.

Peter Michaux June 13, 2008

Steve,

this isn't essential to have OOP in JavaScript as I've shown in the third example for Andrew. I have many of my own uses of this floating around though.

The form and form input namespace collision problems can be fixed by using the forms and elements properties (which I always use.)

var newRef =
  document.forms.myFormObject.elements[fieldName];
Matt Snider June 14, 2008

For the most part I agree with these statements, with a few notable exceptions:

Using 'prototype' and 'new' can be more memory efficient than using a closure, when using the Module Pattern or another namespace pattern, that is initialized or copied many times.

I also sometimes use 'javascript:void(null);' in href tags, as a way to ensure that the 'href' tag doesn't do anything. This is especially useful when using history manager with Ajax, which breaks the use of '#', and 'mousedown' events instead of 'click', because 'mousedown' events aren't stoppable in all browsers.

Michael Girouard June 14, 2008

I also sometimes use ‘javascript:void(null);' in href tags, as a way to ensure that the ‘href' tag doesn't do anything.

Ugh. I can't believe people actually still use that. What's wrong with preventDefault? At the bare minimum return false in your event handlers.

Peter Michaux June 15, 2008

Matt,

I've written an article about JavaScript Widgets Without this. The article shows that, although there is an appearance that the closures require more closures and memory, the OO style requires the same amount of memory. This is due to the necessity of an event library setting this for handlers when using the OO style.

There are likely cases where each system will require more memory than the other. For the most common case of event handlers they are about the same. (Actually the closure system is a little better but I wouldn't use it as an argument in favor of the closure system.)

Matt Snider June 16, 2008

Michael, I haven't been able to get preventDefault to work with onmousedown, in all browsers, and since I don't do inline JavaScript, returning false won't prevent the behavior either.

Usually, I don't use onmousedown for this reason, because preventDefault works just fine with onclick. However, for business purposes, you sometimes need those extra 200ms so that your application appears faster to the user. It often surprises me how much snappier a site feels to a user, just because I use onmousedown instead of onclick.

Peter Michaux June 16, 2008

Matt,

What would preventDefault be preventing for a mousedown event? Even for a link element, it is the click event that has the default of following the link, isn't it?

AngusC May 4, 2010

Peter, this is just great! (on so many levels)

Prototype, this and new are three things I use a lot but I fully get the point you are making.

Switch and case have made me wince ever since I started programming - they are clunky and can verge on the unreadable - and thankfully JavaScript gives you a very elegant way to avoid them (which I've blogged about)

But best of all is your refusal to condemn outright usage of any of these. That is so refreshing. Understanding and thinking about the constructs you use is so much more value-able than parroting half truths about xxxx is evil without really knowing why. Language is subtle and multi-faceted and understanding when to use what is the key.

Thank you!

Have something to write? Comment on this article.