In case you haven't heard, JavaScript is actually being recognized as a powerful language these days, as it possesses powerful Lisp-like qualities. Two features, closures and lambda functions (anonymous functions) are a few key elements behind this power.
To demonstrate this power, I'm going to create some JavaScript code that allows a programmer to easily create JavaScript classes at runtime. I mean classes, not objects. For example, wouldn't it be nice to have a function like this...
function person(first, last, age) {
this.first = first;
this.last = last;
this.age = age;
}
var Bob = new person("Bob", "Jones", 23);
...converted into something more concise like the example below?
var person = make_class("first,last,age");
var Bob = new person("Bob", "Jones", 23);
When you look at this make_class code, I want you to understand that make_class does not create objects, but actual class constructor functions.
JavaScript allows you to create functions on the fly WITHOUT using the dreaded eval function. I won't go into it, but eval is hazzard prone and should be avoided unless there is absolutely no other option. In our case, we don't need it as we can use a subtle, yet powerful feature called closures. Let me explain closures with a simple example.
Let's say you wanted to create a function which increments a hidden internal number everytime the function is called. Let's say you expect you'll need a lot of counters each with their own values. Here's how to do it in JavaScript:
// This function will create our counters.
function make_counter() {
var internal_counter = 0; // We will store our internal counter here
return counter; // We're returning the FUNCTION below, not a value.
function counter() { return ++internal_counter; }
}
Notice how the inner "counter" function uses the local "internal_counter" variable from make_counter. Closures allow inner function to retain variables from its parent function, or even the dreaded global scope. Look at the example of make_counter below to see how it's used. Better yet, try it.
var c1 = make_counter(); // We make our first counter.
var c2 = make_counter(); // We make our second counter.
c1() // Increments and returns 1
c1() // Increments and returns 2
c2() // Increments and returns 1
c2() // Increments and returns 2
Note that c2 has its own internal counter. This is because c2 is actually different function than c1. You can test it by typing in:
c1 == c2
// yields false
As it turns out, whenever you create a function inside another function, you're actually creating an instance of a new function object. Think about that, if you call the outer function 1000 times, 1000 new inner functions will be created. So beware of WHEN AND HOW you're using inner functions.
Back to our make_class function. Using our new knowledge of inner functions and how closures allow inner functions to retain parameters from outer functions, let's try to make a function that creates JavaScript classes. That's classes, not objects.
function make_class(field_names) {
// Turn the comma seperated list into an array
var field_names = field_names.split(",");
return constructor; // Return the constructor function defined below.
function constructor() {
// Enumerate all the arguments given to the constructor
for(var i = 0;i < arguments.length && i < field_names.length;i++) {
// Copy each argument its designated field name
this[field_names[i]] = arguments[i];
}
}
}
Let's look at this function. The first two lines are simple.
With this as a starting point, it shouldn't take a lot of imagination
to see how JavaScript's inherantly dynamic programming model can make
creating complex object models a cinch. Think about it. This is a simple function that's capable of creating a very simple class in a few lines of code. Consider a more complex example that allows you to generate classes from a slightly more complicated specification. Think about it.
with(appmaker){
stringTypes("Name,Phone") // Use default string length
complexType("Address", "Street,City,State,Zip").apply("Address,ShipAddress,BillAddress")
table("Customer").fields("Name,Address,Phone").has("Orders,Invoices")
table("Orders").fields("ShipAddress,BillAddress")
}