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.
"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.
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
.
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];
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.
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.
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.)
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.
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?
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.
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!