How to write JavaScript object definitions
Credit goes to Brandon for pointing this out. Intellisense in VS2010 will work. Export the object definitions out from the function wrapper. As I was working on this, it progressed through 3 options. The first two have lead to option 3, which at the moment is my preferred pattern.
(Option 1)
/* global JsTest */
var JsTest = JsTest || {};
JsTest.Objects = JsTest.Objects || {};
JsTest.Objects = (function () {
"use strict";
var p = {};
p.Rectangle = function (width, height) {
this.width = width;
this.height = height;
};
p.Rectangle.prototype.area = function () {
return (this.width * this.height);
};
p.Rectangle.prototype.circumference = function () {
return ((2 * this.width) + (2 * this.height));
};
p.Square = function (side) {
this.side = side;
};
p.Square.prototype.area = function () {
return this.side * this.side;
};
p.Square.prototype.circumference = function () {
return 4 * this.side;
};
p.Circle = function (radius) {
this.radius = radius;
};
p.Circle.prototype.area = function () {
return (3.14 * (this.radius * this.radius));
};
p.Circle.prototype.circumference = function () {
return (3.14 * (2 * this.radius));
};
return p;
})();
(function () {
"use strict";
var rect1, sq1, cir1, msg;
rect1 = new JsTest.Objects.Rectangle(5, 7);
msg = "Rectangle(" + rect1.width + ", " + rect1.height + ")\n";
msg += "Area: " + rect1.area() + "\n";
msg += "Circumference: " + rect1.circumference() + "\n";
alert(msg);
cir1 = new JsTest.Objects.Circle(8);
msg = "Circle(" + cir1.radius + ")\n";
msg += "Area: " + cir1.area() + "\n";
msg += "Circumference: " + cir1.circumference() + "\n";
alert(msg);
sq1 = new JsTest.Objects.Square(5);
msg = "Square(" + sq1.side + ", " + sq1.side + ")\n";
msg += "Area: " + sq1.area() + "\n";
msg += "Circumference: " + sq1.circumference() + "\n";
alert(msg);
})();
(Option 2:)
/* global JsTest */
var JsTest = JsTest || {};
JsTest.Objects = JsTest.Objects || {};
JsTest.Objects.Rectangle = (function () {
"use strict";
var p = {};
p = function (width, height) {
this.width = width;
this.height = height;
};
p.prototype.area = function () {
return (this.width * this.height);
};
p.prototype.circumference = function () {
return ((2 * this.width) + (2 * this.height));
};
return p;
}());
JsTest.Objects.Square = (function () {
"use strict";
var p = {};
p = function (side) {
this.side = side;
};
p.prototype.area = function () {
return this.side * this.side;
};
p.prototype.circumference = function () {
return 4 * this.side;
};
return p;
}());
JsTest.Objects.Circle = (function () {
"use strict";
var p = {};
p = function (radius) {
this.radius = radius;
};
p.prototype.area = function () {
return (3.14 * (this.radius * this.radius));
};
p.prototype.circumference = function () {
return (3.14 * (2 * this.radius));
};
return p;
}());
(function () {
"use strict";
var rect1, sq1, cir1, msg;
rect1 = new JsTest.Objects.Rectangle(5, 7);
msg = "Rectangle(" + rect1.width + ", " + rect1.height + ")\n";
msg += "Area: " + rect1.area() + "\n";
msg += "Circumference: " + rect1.circumference() + "\n";
alert(msg);
cir1 = new JsTest.Objects.Circle(8);
msg = "Circle(" + cir1.radius + ")\n";
msg += "Area: " + cir1.area() + "\n";
msg += "Circumference: " + cir1.circumference() + "\n";
alert(msg);
sq1 = new JsTest.Objects.Square(5);
msg = "Square(" + sq1.side + ", " + sq1.side + ")\n";
msg += "Area: " + sq1.area() + "\n";
msg += "Circumference: " + sq1.circumference() + "\n";
alert(msg);
}());
(Option 3)
/* global JsTest */
var JsTest = JsTest || {};
JsTest.Objects = JsTest.Objects || {};
JsTest.Objects = (function () {
"use strict";
var p = {};
p.Rectangle = (function () {
var s;
s = function (width, height) {
this.width = width;
this.height = height;
};
s.prototype.area = function () {
return (this.width * this.height);
};
s.prototype.circumference = function () {
return ((2 * this.width) + (2 * this.height));
};
return s;
}());
p.Square = (function () {
var s;
s = function (side) {
this.side = side;
};
s.prototype.area = function () {
return this.side * this.side;
};
s.prototype.circumference = function () {
return 4 * this.side;
};
return s;
}());
p.Circle = (function () {
var s;
s = function (radius) {
this.radius = radius;
};
s.prototype.area = function () {
return (3.14 * (this.radius * this.radius));
};
s.prototype.circumference = function () {
return (3.14 * (2 * this.radius));
};
return s;
}());
return p;
}());
(function () {
"use strict";
var rect1, sq1, cir1, msg;
rect1 = new JsTest.Objects.Rectangle(5, 7);
msg = "Rectangle(" + rect1.width + ", " + rect1.height + ")\n";
msg += "Area: " + rect1.area() + "\n";
msg += "Circumference: " + rect1.circumference() + "\n";
alert(msg);
cir1 = new JsTest.Objects.Circle(8);
msg = "Circle(" + cir1.radius + ")\n";
msg += "Area: " + cir1.area() + "\n";
msg += "Circumference: " + cir1.circumference() + "\n";
alert(msg);
sq1 = new JsTest.Objects.Square(5);
msg = "Square(" + sq1.side + ", " + sq1.side + ")\n";
msg += "Area: " + sq1.area() + "\n";
msg += "Circumference: " + sq1.circumference() + "\n";
alert(msg);
}());
Either define all objects in one closure, and export them to JsTest.Objects (warning: this will overwrite and redefine JsTest.Objects!) or, define each JsTest.Objects.Object in a specific closure, or… a combination of both: one wrapper closure containing all the object definitions, each of which is defined in their own closure.
Matter of programming style?
- Option 1
- It can be difficult just looking at the closure code to separate out the various objects.
- It does allow us share private methods defined in the scope of the closure.
- Option 2
- Somewhat difficult looking at the closure code to decide which object’s code we are looking at. There is a slight effort in looking at the object definition to decide which object definition we are viewing.
- Cannot share private methods.
- Option 3
- Combination of both options 1 & 2. Objects defined in a well contained closures.
- Private methods accessible in the outer closure accessible by object definition inner closures.
- Easy to determine just by looking at the outer closure which object definition we are examining.
A note on the warning: In the earlier post, I had JsTest.Objects.Rectangle defined without using closures (a.k.a. function wrapper). By saying “JsTest.Objects = function () { …” I effectively overwrote the earlier Rectangle definition!!! Note to self: don’t mix styles of defining objects. Pick one, and consistently apply it or risk messing things up!
VS 2010 Intellisense and (function(){}()) wrapper
VS 2010 intellisense will not expose any constructors, properties or methods if the entire module is wrapped in (function(){}());
Define namespaces, and objects in the namespaces, out in the open. The only global will be the namespace definition, and all objects defined in them will be in that scope and not the global namespace scope.
This does not pollute the global namespace.
Consider:
/* global JsTest */
var JsTest = JsTest || {};
JsTest.Objects = JsTest.Objects || {};
JsTest.Objects.Rectangle = function (w, h) {
"use strict";
this.width = w;
this.height = h;
};
JsTest.Objects.Rectangle.prototype.area = function () {
"use strict"; return this.width * this.height;
};
JsTest.Objects.Rectangle.prototype.circumference = function () {
"use strict"; return ((2 * this.width) + (2 * this.height));
};
This code sample defines an object called Rectangle in the namespace JsTest.Objects.
Only JsTest is visible in the global namespace.
To make use of our Rectangle object, we call it as such:
<script src="JsTest.js"></script>
<script type="text/javascript">
var rect1 = new JsTest.Objects.Rectangle(3, 5);</p>
</script>
VS 2010 Intellisense will pickup on our loaded module’s object and it’s properties and methods.
Within a function wrapper, Intellisense works fine. It observes the scope of the variables. It will display them if they are in scope.
Being aware of this will help with coding when the definitions are in one module, and the use of them in another file that includes the module.
Douglas Crockford JavaScript coding conventions
Link to coding conventions: http://javascript.crockford.com/code.html
List of headings at the coding conventions site:
- JavaScript files
- Indentation
- Line Length
- Comments
- Variable Declarations
- Function Declarations
- Names
- Statements
- Simple Statements
- Compound Statements
- Labels
- return Statement
- if Statement
- for Statement
- while Statement
- do Statement
- switch Statement
- try Statement
- continue Statement
- with Statement
- White-space
- Bonus Suggestions
- {} and []
- , (comma) Operator
- Block Scope
- Assignment Expressions
- === and !== Operators
- Confusing Pluses and Minuses
- eval is Evil
JsLint, Missing ‘New’
I had these errors all over the place in JsLint. Did some looking into it.
It’s a matter of coding conventions. Whether I agree with the convention or not, the first letter of a function “capitalized”, is a convention floated by Mr. Crockford’s JsLint that such functions represent an object constructor (and in JS, functions are first-class objects).
IMHO, conventions are nice if followed or understood by most. From here on out I may follow this convention, but I’m not about to go through all my JS code and re-write. Caveat: unless I get paid to do so.
Since JS functions could be anything from plain-old subroutine, an object, an argument in another function, etc… I can see where some type of convention would help.
On the flip side, does it really matter? Wouldn’t the flavor of function being declared be implied in the context of the application in which it was declared? Sort of like the multiple meaning of some words in the English language, where meaning is understood in the context the word was used?
Arrays, delete v. splice
Delete doesn’t really delete an array item like you think it does. It replaces whatever was there with undefined.
Consider:
(function () {
"use strict";
var myArray = ["one", "two", "three", "four"];
delete myArray[2];
window.onload = function () {
var message, htmlMarkup, i;
message = document.getElementById('LabelMessage');
htmlMarkup = "<p>myArray Length: " + myArray.length + "</p>";
for (i = 0; i < myArray.length; i = i + 1) {
htmlMarkup += "<p>myArray[" + i + "]: " + myArray[i] + "</p>";
}
message.innerHTML = htmlMarkup;
};
}());
With the following HTML:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>My Document</title> <script src="Test.js" type="text/javascript"></script> </head> <body> <h1>My JavaScript Tests</h1> <div id="LabelMessage">Test</div> </body> </html>
What we see is an array length of 4 (not 3), and undefined instead of “three”.
To truly remove the third item in the array, and shift the fourth one up or left to fill-in for the third one, use:
myArray.splice(2, 1);
“use strict” in a function?
JsLint started to complain about using the “use strict” in a function form. What???
I found this Douglas Crockford posting from December 2010: “Strict Mode Is Coming To Town”
I was code testing delete Array[i]. Here’s a code sample that implements the recommendation, and passes JsLint:
(function () {
"use strict";
var myArray = ["one", "two", "three", "four"];
}());
So… if developing modules, from here on out, in the module file wrap it all up in a closure, and “use strict” will respect the scope of the closure, and not clobber up all the old stuff that is not ES5 ready… (I think it was referred to as “sloppy” co-mingled with “strict”)
Objects passed by reference
Consider:
var MyObject;
MyObject = MyObject || {};
MyObject.Message = "";
function UpdateMessage(obj) {
obj.Message = "Hello World!";
}
UpdateMessage(MyObject);
window.onload = function () {
var message = document.getElementById("LabelMessage");
message.innerHTML = MyObject.Message;
};
We’ve passed “MyObject” to a function, and changed the value of the property “MyObject.Message” inside the function. When we write this value to the DOM element “LabelMessage”, what we see is:
“Hello World!”
Lexical Scope
Scope defined statically, based on where definition of, say a variable, is within the source code. For example:
var x = 0; function f() { return x; } function g() { var x = 1; return f(); } window.onload = function () { var message = document.getElementById('LabelMessage'); message.innerHTML = g(); }What we see displayed on the web page for “message” is: 0 (zero).
Why:
At the moment f() is defined, the value of “x” in the lexical scope (where it appears in the source code) is the value of “x” defined in line 1.
The scope of “var x = 1;” in function g() is g(). It is not in the same lexical scope of “var x = 0;” from line 1. Therefore, at the time f() is defined the only “var x” in scope is the one defined earlier on line 1.
To test this, we remove the “var” from in front of “x” in g(). Now what “x” in g() is referring to is the “x” on line 1. When we refresh the page, we see “1” displayed.
Another Observation:
In JsLint (www.jslint.com) the option to have one “var” block per function makes perfect sense.
Imagine if there were “var” declarations all over the code and in multiple places in a single function. Try figuring the which of the declared scopes is the right one!
Note on simulating classes
Thinking of C#…
Instance properties would look something like this:
Class(param) {
this.property = param;
}
Instance methods:
Class.prototype.method = function () { /* function body... */ };
Class properties (similar to “static” in C#):
Class.PROPERTY = value;
Class methods – invoked through the class itself, not any particular instance of the class. The “this” keyword does not refer to any particular instance of the class but tot the constructor function itself. Typically “this” is not used at all. Operations are not on instances but on any passed in parameters or variables in scope.
Class.method = function () { /* function body... */ };
The Douglas Crockford videos
Excellent videos to add to the collection.
- Volume One: The Early Years
- Chapter 2: And Then There Was JavaScript
- Act III: Function the Ultimate
- Episode IV: The Metamorphosis of Ajax
- Part V: The End of All Things
Thanks Brandon, for pointing these out.
Leave a Comment