Wednesday, March 08, 2006

Javascript in Ten Minutes

Basic Types

var someNumber = 1;
var anotherNumber = 3.5;

var stringValue = "This is a string\n";
var sqString = 'This is also a string';

someNumber += stringValue; // "1This is a string\n" - JS is weakly typed

Javascript is a dynamically and weakly typed language. Variables are declared with the keyword var, and the type of the variable is inferred at run-time. Common simple types, such as Number, String, and Boolean are supported. The most important type however is the Object type; almost everything in Javascript is an object.

Unlike Python, Javascript is weakly typed: values are implicitly converted as needed.
Arrays

var emptyArray = [];
var homogeneousArray = [1, 2, 3];
var heterogeneousArray = ["one", 2, 3.0];

Javascript has built-in Array objects. Arrays in Javascript are different from C or Java arrays - a Javascript array is actually a collection of arbitrary values. The values don't have to be of the same type, and the array length may change at run time.

Many utility methods are available on arrays, for example:

var a = [3, 2, 1];
a.push(7); // [3, 2, 1, 7]
a.sort(); // [1, 2, 3, 7]
["a", "b"].indexOf("b"); // 1

Arrays can be created with the bracket notation [] or using the Array constructor (e.g. new Array(5) or just Array(1,2,3)).
Objects

var emptyMap = {};
var homogeneousMap = {"one": 1, "two": 2, "three": 3};
var heterogeneousMap = {"one": 1,
"two": "two",
"three": 3.0};

One of the most important concepts in Javascript is the "object". Each object is basically a set of key-value pairs. It is called a Map in Java, a hash in Perl, and a dictionary in Python.

Keys can be arbitrary strings, values can be anything.

Objects can be created, in particular, using the {} literal notation, using the Object and other contructors with the new operator:

var simpleObject = {a:1}, emptyObject = new Object();
var dateObject = new Date();

Properties access

// Dot notation property access
window.alert("homogeneous map property \"one\" "
+ homogeneousMap.one);
// Subscript notation property access
var prop = "one two three";
window.alert("homogeneous map property \"one two three\" "
+ homogeneousMap[prop]);

Properties assignment

Similarly, you can assign to properties of an object:

homogeneousMap["red blue"] = 10;
homogeneousMap.two = 20;

Properties removal

delete homogeneousMap["one"];
delete homogeneousMap.two;

Iteration

for (var key in heterogeneousMap) {
window.alert("heterogeneous map property \""
+ key
+ "\" = "
+ heterogeneousMap[key]);
}

Only iterates over enumerable (8.6.1) properties. For example, length is a property of an array, but a for..in loop doesn't iterate over it:

var arr = [a, b]; alert(arr.length); // 2
for(var prop in arr) alert(prop); // alerts 1 and 2.

Functions

Functions are first-class objects. That means that they can be created dynamically, stored, passed and returned just like any other value.

var callable = function (message) { // <-- notice assignment
window.alert("Callable called with message = "
+ message);
}

function callCallable(f, x) {
f(x);
}
callCallable(callable, "Hello world!");

Closures are supported:

function createClosure(initial) {
var res = function () {
initial = initial + 1;
window.alert("Closure with modified state "
+ initial);
}
return res;
}
var f = createClosure(1);
f(); f(); // alerts 2 and 3

OOP in Javascript

function MyObject(name, value) {
this.name = name;
this.value = value;
}

Javascript's object system is prototype-based, meaning it is very different from the conventional OO languages like Java or C++. Not a class type but an object constructor is created for new objects with particular properties. In the example above the this keyword used to reference the instance of the object being created. The this object is essentially a property map with members accessed (and initialized) in this example with the dot notation.

The object constructor, MyObject, is an object constructor not in how it's defined, which looks like any other Javascript function, but in how it's ''invoked''.

var my = new MyObject("foo", 5);

The new operator before the function invokes the function with a newly constructed object as this and returns the initialized object.
Object Prototype

Part of what makes a language object oriented is that data not only has properties but also ''behaviors''. Also known as: member functions; methods; and object messages. To implement a member function in Javascript, one would be tempted to write something like what's below based on the member initialization exampled above.

function BadObject(data) {
this.data = data
this.memberFunction = function () {
// ...functions on data...
}
}

While the code above will work without error, it does create a new closure for each member function for each new instance of the object. What's really required is a class level function that works on instance data. But remember, Javascript objects aren't class based but prototype based. So how to we implement "class" level member functions? (Skip to Implementation) Better yet, how do we implement "class" level members functions in general?

Enter the prototype member.

The internal object member, prototype, has language defined significance in that it is used for resolving property names if the property isn't found in the current property map. It's considered internal because, while the instance's prototype member is ''inherited'' from the ''constructor's'' prototype member, it cannot be accessed directly from the object instance itself. The defined prototype member is a property map itself which holds members for property name resolution. Consider the example below:

var parentPropertyMap = {"bar": "I'm the bar"};

// Define the constructor with inheritable properties
function ChildObject(foo) {
this.foo = foo;
}
ChildObject.prototype = parentPropertyMap;

childPropertyMap1 = new ChildObject("I'm the foo1");
childPropertyMap2 = new ChildObject("I'm the foo2");

// Prints "childPropertyMap1.foo = I'm the foo1"
window.alert("childPropertyMap1.foo = " + childPropertyMap1.foo);

// Prints "childPropertyMap2.foo = I'm the foo2"
window.alert("childPropertyMap2.foo = " + childPropertyMap2.foo);

// Prints "childPropertyMap1.bar = I'm the bar"
window.alert("childPropertyMap1.bar = " + childPropertyMap1.bar);

// Prints "childPropertyMap2.bar = I'm the bar"
window.alert("childPropertyMap2.bar = " + childPropertyMap2.bar);
The member foo is an instance member added to the
instance's property map during construction:

function ChildObject(foo) {
this.foo = foo;
}

while bar is in the constructor's prototype:

var parentPropertyMap = {"bar": "I'm the bar"};
...
ChildObject.prototype = parentPropertyMap;

which is ''inherited'' during the new operation:

childPropertyMap1 = new ChildObject("I'm the foo1");
childPropertyMap2 = new ChildObject("I'm the foo2");

In other words, the member, bar, is shared across all instances of ChildObject.

Therefore, by implementing the prototype member of the constructor function, we can think of the constructor function itself as the "class" object. Complete with static class functions:

function ClassObject() {}
ClassObject.staticClassFunction = function(x) {
return x * 2;
}

static class variables:

function ClassObject() {}
ClassObject.staticClassVariable = 5;

shared member variables:

function ClassObject() {}
ClassObject.prototype.sharedMember = 5;

and of course, shared member functions:

function ClassObject(x) {
this.x = x;
}
ClassObject.prototype.memberFunction = function(x) {
return x * this.x;
}

Member Function Implementation

function Message(message) {
this.message = message;
}

Message.prototype.show = function() {
window.alert("Message.show() with message = "
+ this.message);
}

(More on Classes and Objects)
Example Code

//////////////////////////////////////
// Basic Types
var intValue = 1;
var floatValue = 3.0;
var stringValue = "This is a string\n";

///////////////////////////////////////
// Array
var emptyList = [];
var homogeneousList = [1, 2, 3];
var heterogeneousList = ["one", 2, 3.0];

///////////////////////////////////////
// Property Map
//
var emptyMap = {};
var homogeneousMap = {"one": 1, "two": 2, "three": 3};
var heterogeneousMap = {"one": 1,
"two": "two",
"three": 3.0};

///////////////////////////////////////
// Functions as values
//
var callable = function (message) { // <-- notice assignment
window.alert("Callable called with message = "
+ message);
}

function createClosure(initial) {
var res = function () {
initial = initial + 1;
window.alert("Closure with modified state "
+ initial);
}
return res;
}

///////////////////////////////////////
// Functions as arguments
//
function callCallable(f, x) {
f(x);
}

function composeCallables(f, g, x) {
f(g(x));
}

///////////////////////////////////////
// Objects
//
function MyObject(name, value) {
this.name = name;
this.value = value;
}

///////////////////////////////////////
// Objects with Member Functions
//
function Message(message) {
this.message = message;
}

Message.prototype.show = function() {
window.alert("Message.show() with message = "
+ this.message);
}

///////////////////////////////////////
// Demo Utilities
//
function quote(message) {
return "\"" + message + "\"";
}

///////////////////////////////////////
// HTML Invoked demonstration
// body onload="main()"
//
function main() {
window.alert("Integer = " + intValue);
window.alert("Float = " + floatValue);
window.alert("String = " + stringValue);

for (var item in emptyList) {
window.alert("Empty list item = " + item);
}

// Script style index iteration
for (var i in homogeneousList) {
window.alert("homogeneous list item = "
+ homogeneousList[i]);
}

// C style index iteration
for (var i=0; i < heterogeneousList.length; ++i) {
window.alert("heterogeneous list item = "
+ heterogeneousList[i]);
}

// Ruby-ish style index iteration
function each(a, f) {for(var i=0; i < a.length; i++) f(a[i])};

each(heterogeneousList, function(e) {
window.alert("heterogeneous list item = " + e);
})

// Dot notation property access
window.alert("homogeneous map property \"one\" "
+ homogeneousMap.one);
// Subscript notation property access
window.alert("homogeneous map property \"two\" "
+ homogeneousMap["two"]);

for (var key in heterogeneousMap) {
window.alert("heterogeneous map property \""
+ key
+ "\" = "
+ heterogeneousMap[key]);
}

callable("(Function value invoked)");
closure();
closure();

callCallable(closure);
composeCallables(callable, quote, "My Message");

var my = new MyObject("foo", 5);
window.alert("MyObject my.name = " + my.name);
window.alert("MyObject my[\"value\"] = " + my["value"]);

var msg = new Message("bar");
for (var key in Message.prototype) {
window.alert("Message prototype member \""
+ key
+ "\" = "
+ Message.prototype[key]);
}

window.alert("Message msg.message = " + msg.message);
msg.show();
}