Monday, March 15, 2010
Tuesday, March 2, 2010
A few JavaScript Gotchas
JavaScript was originally written in a hurry as a browser tweaker for Netscape. Then ECMA blindly copied their efforts (warts and all) to form the first standard. Subsequent reluctance to trim the nastiness for fear of breaking legacy support has meant JavaScript has more than its share of goblins waiting to trip you up.
1. Missed Semicolons
Have you ever revisited your JavaScript code and noticed you missed a semicolon or two from statement endings? Then wondered how come it still worked all this time. JavaScript tries to be forgiving and when it sees a line break without a semicolon will assume you forgot it - and add it for you. As anyone who uses MS Word will tell you, software that goes out of its way to be extra helpful often turns out to be a hindrance.
Take this:
function getActualWidth(innerWidth, specialMargins, extraPadding, lastMinuteBuffering) {
return
innerWidth+
specialMargins +
extraPadding +
lastMinuteBuffering ;
}
return
innerWidth+
specialMargins +
extraPadding +
lastMinuteBuffering ;
}
Looks safe enough right? but actually it will always return undefined.
Thats because the JavaScript compiler sees the unterminated return statement and kindly adds the "missing" semicolon for you. Any line of code which could form a standalone statement is subject to this form of misguided generosity (so the lines ending in + are left alone)
Moral: Watch where you put your linebreaks and never rely on JavaScript to clean up after you.
2. Over Coercion
Coercion - the act of converting one type to another in arithmetic or comparator expressions is sometimes useful. Unfortunately JavaScript drunk a little too much of the coercion kool-aid. Bear in mind = = means compare by coercion, = = = means compare and don't coerce (types must be the same)
true == 1; //true! (converts true to bit value 1)
true - false === 1 //true!!!
"2" + 2; //22!
"2" - 2; //0 ;-(
"2" - - 2; //4 :-)
NaN == NaN; //false - this one sort of makes sense since NaN is any non-Number cast to a number
NaN < NaN; //false
true - false === 1 //true!!!
"2" + 2; //22!
"2" - 2; //0 ;-(
"2" - - 2; //4 :-)
NaN == NaN; //false - this one sort of makes sense since NaN is any non-Number cast to a number
NaN < NaN; //false
NaN > NaN; //false - whatever!
"" == 0 //true
undefined == null //false!
"0" == 0 //true!
"false" = false //false!!!' undefined == null //false!
"0" == 0 //true!
'\t\r\n ' == 0 //true!!! - essentially an empty string which coerces to 0
Are we having fun yet?
Well it doesn't end there. JavaScript will also try to coerce anything in an if statement to a boolean. Sometimes this is very useful - but developers need to be clear about what is happening - because it's easy to trip up
if (0) {....} //false
if(-1) {....} //true
if("") {....} //false
if("null") {....} //trueBear this in mind if you are testing an argument against null pointer. Its easy to accidentally skip 0 values
Moral: == and is(var) can be useful operators for detecting similar values of different types and non boolean equivalents. But you need to understand what is going on under the covers. If you don't, use === and if(var !== null) instead.
3. Auto Globalling
Any time you forget to pre-empt a variable with var it gets defined in the global scope (regardless of closures). This "feature" is known as auto-globalling and its guaranteed to get you at least twice.
Sometimes auto-globalling sneaks in where you least expect it
function doThings() {
var a = b = 1;
//a = 1 (local variable)
//b = 1 (global variable!!!)
}
var a = b = 1;
//a = 1 (local variable)
//b = 1 (global variable!!!)
}
This is because javascript always evaluates expressions form right to left. So it starts by assigning 1 to b (not var b - so its global). Then it assigns global b to local a
Its the same as
b = 1;
var a = b;
var a = b;
Tuesday, February 23, 2010
Five ways to create objects - Part 2: Inheritance
Let me start by saying I think inheritance is somewhat overrated in JavaScript. A lot of the inheritance you are going to need is already created for you: Function, String, Number etc. all inherit Object via its prototype.
I actually wonder if more energy goes into intellectual exercises around JavaScript inheritance than into using inheritance for real time solutions. Yes, JavaScript has an excellent inheritance mechanism but lets face it all those examples with furry animals, hierarchies of wheeled vehicles and the like have little real world application in client side coding.
How often do your new objects really need to inherit from other new objects you have created? By its nature the client object modelling is essentially flat (like your monitor). If you find yourself in JavaScript creating complex java-style object models with layers of inheritance then you might want to ask yourself why. Ajax allowed us to defer to the server rather when we used to have to clone our business/server logic on the client. I'd argue such complex data structures are best left to the server, because they perform better, are more easily distributed across subsystems and are probably more suited to classical OOP.
With that said, JavaScript does offer a very nifty inheritance strategy - there are no classes - objects inherit form objects. Period. Its clean and its simple.
So here goes..
Last time I demonstrated five ways to create objects in JavaScript. Now, as promised here's how to apply inheritance in each case. Clearly some cases are more useful than others.
Lets say our notepad from last week's example might need to inherit some properties from a more generic "panel" component.
1. Simple Object Literal
2. Nested Object Literal
3. Constructor using Object Literal (courtesy of Douglas Crockford)
4. Simple Constructor for new
5. Prototype with Constructor for new
I actually wonder if more energy goes into intellectual exercises around JavaScript inheritance than into using inheritance for real time solutions. Yes, JavaScript has an excellent inheritance mechanism but lets face it all those examples with furry animals, hierarchies of wheeled vehicles and the like have little real world application in client side coding.
How often do your new objects really need to inherit from other new objects you have created? By its nature the client object modelling is essentially flat (like your monitor). If you find yourself in JavaScript creating complex java-style object models with layers of inheritance then you might want to ask yourself why. Ajax allowed us to defer to the server rather when we used to have to clone our business/server logic on the client. I'd argue such complex data structures are best left to the server, because they perform better, are more easily distributed across subsystems and are probably more suited to classical OOP.
With that said, JavaScript does offer a very nifty inheritance strategy - there are no classes - objects inherit form objects. Period. Its clean and its simple.
So here goes..
Last time I demonstrated five ways to create objects in JavaScript. Now, as promised here's how to apply inheritance in each case. Clearly some cases are more useful than others.
Lets say our notepad from last week's example might need to inherit some properties from a more generic "panel" component.
1. Simple Object Literal
var myApp = {};
myApp.panel = {};
myApp.panel.toggleDisplay = function() {
this.displayed = (this.displayed==="none")? "" : "none";
}
myApp.panel.defaultWidth = 300;
myApp.notepad = {};
myApp.notepad.writeable = true;
myApp.notepad.font = 'helvetica';
myApp.notepad.setFont = function(theFont) {
myApp.notepad.font = theFont;
}
//OK not inheritance at all. But best we can do, since notepad has no relation to panel.
myApp.panel.toggleDisplay.apply(myApp.notepad);
myApp.notepad.defaultWidth = myApp.panel.defaultWidth;
myApp.panel = {};
myApp.panel.toggleDisplay = function() {
this.displayed = (this.displayed==="none")? "" : "none";
}
myApp.panel.defaultWidth = 300;
myApp.notepad = {};
myApp.notepad.writeable = true;
myApp.notepad.font = 'helvetica';
myApp.notepad.setFont = function(theFont) {
myApp.notepad.font = theFont;
}
//OK not inheritance at all. But best we can do, since notepad has no relation to panel.
myApp.panel.toggleDisplay.apply(myApp.notepad);
myApp.notepad.defaultWidth = myApp.panel.defaultWidth;
2. Nested Object Literal
var myApp = {};
myApp.panel = {
toggleDisplay : function() {
this.displayed = (this.displayed==="none") ? "" : "none";
},
defaultWidth : 300
};
myApp.notepad = {
writeable: true,
font: 'helvetica',
setFont: function(theFont) {
this.font = theFont;
}
};
//Same brute-force inheritance as example (1)
myApp.panel.toggleDisplay.apply(myApp.notepad);
myApp.notepad.defaultWidth = myApp.panel.defaultWidth;
myApp.panel = {
toggleDisplay : function() {
this.displayed = (this.displayed==="none") ? "" : "none";
},
defaultWidth : 300
};
myApp.notepad = {
writeable: true,
font: 'helvetica',
setFont: function(theFont) {
this.font = theFont;
}
};
//Same brute-force inheritance as example (1)
myApp.panel.toggleDisplay.apply(myApp.notepad);
myApp.notepad.defaultWidth = myApp.panel.defaultWidth;
3. Constructor using Object Literal (courtesy of Douglas Crockford)
var myApp = {};
myApp.Panel = function(defaultWidth ) {
var that = {};
that.defaultWidth = defaultWidth ;
that.toggleDisplay = function() {
that.displayed = (that.displayed==="none") ? "" : "none";
}
return that;
}
myApp.Notepad = function(defaultFont, width) {
var that = myApp.Panel(300);
that.writeable = true;
that.font = defaultFont;
that.setFont = function(theFont) {
that.font = theFont;
}
return that;
}
//true inheritance without using new or prototype (courtesy of Douglas Crockford)
myApp.notepad1 = myApp.Notepad('helvetica',300);
myApp.notepad1.defaultWidth;
myApp.Panel = function(defaultWidth ) {
var that = {};
that.defaultWidth = defaultWidth ;
that.toggleDisplay = function() {
that.displayed = (that.displayed==="none") ? "" : "none";
}
return that;
}
myApp.Notepad = function(defaultFont, width) {
var that = myApp.Panel(300);
that.writeable = true;
that.font = defaultFont;
that.setFont = function(theFont) {
that.font = theFont;
}
return that;
}
//true inheritance without using new or prototype (courtesy of Douglas Crockford)
myApp.notepad1 = myApp.Notepad('helvetica',300);
myApp.notepad1.defaultWidth;
4. Simple Constructor for new
var myApp = {};
myApp.Panel = function(defaultWidth) {
this.defaultWidth=defaultWidth ;
this.toggleDisplay = function() {
this.displayed = (this.displayed==="none") ? "" : "none";
}
}
myApp.Notepad = function(defaultFont) {
this.writeable = true;
this.font = defaultFont;
this.setFont = function(theFont) {
this.font = theFont;
}
}
myApp.notepad1 = new myApp.Notepad('helvetica');
//Without prototype we can only kluge inheritance here. Example (5) will fix it.
myApp.notepad1.defaultWidth; //undefined
myApp.Panel = function(defaultWidth) {
this.defaultWidth=defaultWidth ;
this.toggleDisplay = function() {
this.displayed = (this.displayed==="none") ? "" : "none";
}
}
myApp.Notepad = function(defaultFont) {
this.writeable = true;
this.font = defaultFont;
this.setFont = function(theFont) {
this.font = theFont;
}
}
myApp.notepad1 = new myApp.Notepad('helvetica');
//Without prototype we can only kluge inheritance here. Example (5) will fix it.
myApp.notepad1.defaultWidth; //undefined
5. Prototype with Constructor for new
//utility function
function deepClone(obj) {
var clone = {};
for(var i in obj) {
if(typeof(obj[i])==="object") {
clone[i] = obj[i].deepClone();
} else {
clone[i] = obj[i];
}
}
return clone;
}
var clone = {};
for(var i in obj) {
if(typeof(obj[i])==="object") {
clone[i] = obj[i].deepClone();
} else {
clone[i] = obj[i];
}
}
return clone;
}
myApp = {};
myApp.Panel = function(defaultWidth) {
this.defaultWidth = defaultWidth;
}
myApp.Panel.prototype.toggleDisplay = function() {
this.displayed = (this.displayed==="none") ? '' : "none";
alert('display = ' + (this.displayed ? 'on' : 'off'));
}
myApp.Notepad = function(defaultFont,defaultWidth) {
myApp.Panel.call(this,defaultWidth); //inject self into Panel constructor
this.font = defaultFont;
}
//inherit from Panel....
//better to simply grab Panel's prototype rather than create new instance of Panel
myApp.Notepad.prototype.writeable = true;
myApp.Notepad.prototype.setFont = function(theFont) {
this.font = theFont;
}
//true inheritance - this time using prototype
myApp.notepad1 = new myApp.Notepad('helvetica',300);
myApp.notepad1.defaultWidth; //300
myApp.Panel = function(defaultWidth) {
this.defaultWidth = defaultWidth;
}
myApp.Panel.prototype.toggleDisplay = function() {
this.displayed = (this.displayed==="none") ? '' : "none";
alert('display = ' + (this.displayed ? 'on' : 'off'));
}
myApp.Notepad = function(defaultFont,defaultWidth) {
myApp.Panel.call(this,defaultWidth); //inject self into Panel constructor
this.font = defaultFont;
}
//inherit from Panel....
//better to simply grab Panel's prototype rather than create new instance of Panel
myApp.Notepad.prototype = myApp.Panel.prototype.deepClone();
myApp.Notepad.prototype.writeable = true;
myApp.Notepad.prototype.setFont = function(theFont) {
this.font = theFont;
}
//true inheritance - this time using prototype
myApp.notepad1 = new myApp.Notepad('helvetica',300);
myApp.notepad1.defaultWidth; //300
Monday, February 15, 2010
Five ways to create objects....
1) and 2) are best suited for creation of one-time objects. 3), 4) and 5) define templates for creation of multiple objects with a shared design.
All are useful and none are wrong.. If I only needed one notebook in my app, I'd lean towards using 2) since it neatly encapsulates all properties within its defining closures. For multiple notebook "instances" I like 5) simply because I can lock generic properties into the prototype object, making the constructor cleaner and more efficient
Next time I'll talk about how to employ object inheritance with each methodology.
myApp.Notepad = function(defaultFont) {
this.font = defaultFont;
}
myApp.Notepad.prototype.writeable = true;
myApp.Notepad.prototype.setFont = function(theFont) {
this.font = theFont;
}
myApp.notepad1 = new myApp.Notepad('helvetica');
All are useful and none are wrong.. If I only needed one notebook in my app, I'd lean towards using 2) since it neatly encapsulates all properties within its defining closures. For multiple notebook "instances" I like 5) simply because I can lock generic properties into the prototype object, making the constructor cleaner and more efficient
Next time I'll talk about how to employ object inheritance with each methodology.
1. Simple Object Literal
myApp.notepad = {};
myApp.notepad.writeable = true;
myApp.notepad.font = 'helvetica';
myApp.notepad.setFont = function(theFont) {
myApp.notepad.font = theFont;
}2. Nested Object Literal
myApp.notepad = {
writeable: true,
font: 'helvetica',
setFont: function(theFont) {
this.font = theFont;
}
}
3. Constructor using Object Literal
myApp.Notepad = function(defaultFont) {
var that = {};
that.writeable = true;
that.font = defaultFont;
that.setFont = function(theFont) {
that.font = theFont;
}
return that;
return that;
}
myApp.notepad1 = myApp.Notepad('helvetica');
4. Simple Constructor for new
myApp.Notepad = function(defaultFont) {
this.writeable = true;
this.font = defaultFont;
this.setFont = function(theFont) {
this.font = theFont;
}
}
myApp.notepad1 = new myApp.Notepad('helvetica');
this.writeable = true;
this.font = defaultFont;
this.setFont = function(theFont) {
this.font = theFont;
}
}
myApp.notepad1 = new myApp.Notepad('helvetica');
5. Prototype with Constructor for new
this.font = defaultFont;
}
myApp.Notepad.prototype.writeable = true;
myApp.Notepad.prototype.setFont = function(theFont) {
this.font = theFont;
}
myApp.notepad1 = new myApp.Notepad('helvetica');
Wednesday, February 10, 2010
Argument management
So you wanted to beef up your JavaScript utility by adding a bunch of config options and now you have a constructor with 10 arguments.
Fortunately JavaScript does not support function overloading, so you only have to supply arguments up to the last non null value. Unfortunately, if you only want to provide arguments 1, 6, 7 and 10 you also need to specify a lot of null arguments in between - and you need to count the null arguments very carefully to be certain exactly which value you are assigning to which argument. And pity anyone who is trying to read your code.
function CleverThing(element, duration, rotation, suspension, gravitation, gradation, hesitation, gestation, scale, color) {
this.element = element;
this.duration = duration;
this.rotation = rotation;
... ...
}
var newThing = new CleverThing('arrow', null, null, null, null, 80, 200, null, null, '#FF0000' );
Object literals to the rescue. Merge all your optional arguments into one, call it something like 'options', and assign attributes only as required
function CleverThing(element, options) {
this.element = element;
this.duration = options.duration;
this.rotation = options.rotation;
... ...
}
var newThing = new CleverThing('arrow',{gradation:80, hesitation:200, color:'#FF0000'});
Now you only need pass what you need and anyone viewing the statement can plainly see the intention. Oh and of course you can now pass the options in any order.
The Case against Switch
I've never been fond of switch statements, whether in JavaScript or Java. They're big and hard to follow, and of course if you forget the break keyword after each case you enter fall-through hell. (Since break statements are almost always intended it seems a pain to have to add them manually)
Fortunately JavaScript offers the simplicity and elegance of Object literals as an alternative.
Example 1: Using switch is hard to read and the data is mixed with the logic
Example 2: Pull data into object construct. Logic becomes clean
Fortunately JavaScript offers the simplicity and elegance of Object literals as an alternative.
Example 1: Using switch is hard to read and the data is mixed with the logic
var whatToBring;
switch(weather){
case "Sunny":
whatToBring = "Sunscreen and hat";
break;
case "Rain":
whatToBring ="Umbrella and boots"
break;
case "Cold":
whatToBring = "Scarf and Gloves";
break;
default : whatToBring = "Play it by ear";
}
switch(weather){
case "Sunny":
whatToBring = "Sunscreen and hat";
break;
case "Rain":
whatToBring ="Umbrella and boots"
break;
case "Cold":
whatToBring = "Scarf and Gloves";
break;
default : whatToBring = "Play it by ear";
}
Example 2: Pull data into object construct. Logic becomes clean
var gear = {
"Sunny" : "Sunscreen and hat",
"Rain" : "Umbrella and boots",
"Cold" : "Scarf and Gloves",
"Default" : "Play it by ear"
}
var whatToBring =
gear[weather] ? gear[weather] : gear["Default"];
"Sunny" : "Sunscreen and hat",
"Rain" : "Umbrella and boots",
"Cold" : "Scarf and Gloves",
"Default" : "Play it by ear"
}
var whatToBring =
gear[weather] ? gear[weather] : gear["Default"];
Subscribe to:
Posts (Atom)