The Joy of Javascript
or... the joy of ECMAscript or Standard ECMA-262 Script
I may have realised I really liked javascript before I started reading him, but it was only after I found Douglas Crockford's articles on the subject that things perhaps crystallized.
I can sum up the point of my blog post here by quoting from one of Douglas Crockford's articles:
"... JavaScript has closures. What this means is that an inner function always has access to the vars and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language. There is no book currently available on JavaScript programming that shows how to exploit it. Most don't even mention it."
-- Douglas Crockford From: http://www.crockford.com/javascript/private.html
This was written many whiles ago, so it's quite possible that there are books that talk about this now - I don't know for sure...
Before I get back to the closure, I'm going to reel off some of the things I like about javascript.
[0] Has the basics
A good scripting language should make it easy for you to wield both normal index arrays and associative arrays (hashes, dictionaries or whatever you want to call it). You can do a lot with just these 2 datastructures. It's the bread and butter of scripting.
Array behaviour:
var ar = [];
ar.push(1);
ar.push('foo');
Associative array behaviour:
var assoc = {};
assoc['foo'] = 'bar';
Javascript's associative array ({}) is also it's basic object which you can easily modify...
[1] Simple Objects - can be handy
A simple javascript object is easy as:
var obj = {};
This is the same as its associative array. And you can add to it like this:
obj.param1 = 'some parameter';
This is the object-style of adding and amending simple objects in javascript, using the dot operator.
This form is the same as
obj['param1'] = 'some parameter';
which is the associative array format. Here is yet another way using object literals:
var obj = { param1: 'some parameter' };
Nice, light-weight, easy. No fuss. It's an easy way to bundle some things together quickly.
[2] Simplicity and consistency
Javascript (ECMA-262) is a one-trick pony. All it's goodness is built around one simple thing: the closure. In fact, pretty much everything else it has is fairly ordinary or lacking.
Some languages have a feeling of more consistency than others. On the "consistent" side we have languages like ruby/smalltalk ("everything is an object"), lisp (no syntax, macros and lexical closures, "everything is a list" ), haskell (pure/functional), javascript ("the closure, stupid!") etc etc and on the other we have: perl, php and maybe languages like visual basic 6 which feel a bit cobbled together or perhaps lack magic or have too much crudely bolted on in some unexpected, hard-to-read way.
So, what is javascript's one trick... Javascript has closures and variables; a root Object and a prototype system if you care to use it.
Behold the closure
var F = function() {}
That's it.
You can call it:
var f = F();
Closure as object
If you call F and it returns something that lives on in your
program and that refers to something defined locally inside of F()
,
then you have a closure. It's kind of like a forerunner to an object
instance (as recognised by a java or ruby programmer) because some state
and functionality lives on specific to that particular invocation of
F()
.
Here's a little pattern for creating object-like entities (I might also refer to these as quasi-objects).
var F = function() {
me = {};
var secret = 1;
me.public_property = 2;
me.privileged_func = function() {
return secret;
}
return me;
}
var f = F();
f.privileged_func();
privileged_func
is privileged because it has access to the
secret
variable.
Every time we make a new f
, a new copy of privileged_func
will
be created. This isn't necessarily a problem but if want to
optimise, and your objects you can add functions outside of the
closure created by F()
.
F.methods = {
func1:functions() { return this.public_property; },
func2:functions() {....}
}
F.make = function() {
var n;
var f = F();
for(n in F.methods) f[n] = F.methods[n];
return f;
}
f = F.make();
Here we introduce F.make
which does the job of adding in
public functions to an instance of F()
. Note that these
public functions can refer to the object they have been
assigned to via the this
keyword. Above, func1
, accesses
the public_property
of an instance of F
.
Javascript also allows you to instantiate objects using the new
keyword. This creates object instances (in which case F is
referred to as a constructor and f as an 'object instance' often
shortened to 'object'). Instantiating with the new
keyword tells
javascript to create an object that uses javascript's prototype
system.
var f = new F();
Note that the function defined inside F will not be available. A privileged function pattern for object instances is shown near the end of this article.
Whether you work with F either calling it directly or with the 'new' operator, you are still working with the same thing, that works the same way: the closure.
And that's it. This is what makes javascript simple and sort of consistent (to use this term very loosely): the closure, the most protean thing known to computer science.
[3] Prototypically Overrated
It seems that javascript's coolness is often touted along the lines of its prototype system or alternatively, that it can be made to mimic classically object oriented programming languages.
Prototypes allow you to share functions across multiple instances.
We did this manually in the above example using F.make()
.
If you use the 'new' operator, you get this for free via javascript's
prototype system.
Prototypes are a good way to conserve memory and define functions
that get shared by all object instances created from
a constructor similar to F.make()
.
But prototypes are limited in the same way the F.make()
strategy is limited.
If those methods you add to the prototype need to access
the state of the object instance, that state will need to be
publicly available.
Prototypes and inheritance (prototype or classical)
My preference when writing a complex system of objects and functions, is to hide all but the methods that an object is prepared to expose to the world outside of it. Everything else should be hidden using javascript's closures.
Not doing this is like asking your object to wear all it's innards and internal organs on the outside for all to see and run amok with as they choose.
Minimizing and making explicit the interface your objects, quasi-objects and modules provide to the outside world, helps you to more easily reason through your code and the systems you construct with those objects and modules. And it allows you to more easily re-implement an object whilst maintaing its interface to said world. (If you are wondering what is meant by "modules", this is covered further down.)
You solve exposed state problem by not using prototypes and instead
adopting a privileged function pattern.
The prototype chain (prototype inheritance) is also touted as a benefit; as javascript's version of classical inheritance. Whilst this is useful, for code reuse (where you specialise an existing object to solve a new problem), and for things like convenience wrapper functions, overuse of inheritance (prototype or classical) can make your code harder to read and reason through.
You not only have to think "horizontally" about the interfaces that your objects and modules expose and how they are used; but you also have to think "vertically" up a class or object hierarchy.
Prototypes encourage you not to hide state and to potentially build "vertical" hierarchies.
Prototype example
- Here's a basic prototype example where we set an object 'proto' which contains some useful methods as the prototype for the F constructor.
privval
is not accessible by F's prototype objectF.prototype
- Only values defined through the
this
keyword in the F constructor will be accessible to the functions defined inF.prototype
. - These values are publicly available to anything that can see or access an instance of F. So we can't hide the state.
- If you replace the anonymous object literal assigned to
F.prototype
with an object instance of another constructor (sayg = new G()
), you can start to build a chain of prototypes. The methods in G.prototype will be available to both f and g.
var F,f;
// Constructor F:
F = function() {
var privval = 1;
this.pubval = 'mess with me!';
}
F.prototype = {
meth1: function() { return this.pubval; }
}
f = new F();
f.meth1(); // returns this.pubval
f.pubval = "here's a change!";
Maker function pattern to the rescue
This article appears to be suggesting that - through an extra closure - we can encapsulate an object's state and still use a prototype to access it. Basically, we use a closure courtesy of calling a maker function, to encapsulate the two. I'm not entirely sure at this point how it would be done.
- Update I've written some more about this in a later article
- Here I set up javascript properties (using
defineProperty
) and prototypes that have privileged access to information using closures.
- Here I set up javascript properties (using
[4] Half-way functional
Javascript sits between functional and object oriented programming worlds. You can whip up anonymous functions and pass them around with gay abandon. This type of thing can lead to patterns and techniques that allow you to do powerful things. If you understand the concept behind closures you'll start to get the hang of it and you won't look back.
Closure gotchas
It can also get a little confusing. If an outer closure has a variable that changes - say in a for-loop - and during each iteration of the for-loop you generate a new inner function (not closure) that will get executed later (eg as an event handler) and which refers to something that changes with the for-loop (eg the looping variable), you'll find all your inner functions will refer to last item in the for-loop. Unfortunately, you need to manufacture an inner closure and pass in the changing variable into it so that a copy is taken and stored in that closure. This inner closure returns the inner function which you can then use. This type of thing can get expensive too.
Here's an example:
var cf1 = [];
var cf2 = [];
function createFuncs1() {
var i;
for(i=0;i<10;i++) {
cf1.push(function(){alert(i);});
// GOTCHA: 'i' refers to whatever state
// 'i' was left in the closure associated with
// createFuncs1.
// This will be 10.
}
}
// Add a closure to solve the problem...
function createFuncs2() {
var i;
for(i=0;i<10;i++) {
cf2.push(
function(i2){
return function(){alert(i2);};
}(i)
// i2 is a local variable of the inner closure
// that is created with the invocation using 'i'.
);
}
}
createFuncs1();
cf1[0](); // 10
cf1[1](); // 10
createFuncs2();
cf2[0](); // 0
cf2[1](); // 1
cf2[2](); // 2
Similarly with objects. You can mimick some of the things that "real" OOP languages have if you really want to force it or you can use javascripts prototype system - see the links and discussion above.
[5] The module pattern (variation on yahoo pattern)
Modules created using the module pattern are invoked once (when your program is read in to the interpreter), returning an object that gives access to stuff living inside that closure. It can be pretty much anything too. You can use modules created by this pattern to store functions, variables and objects (both the constructors and object instances). You can make all of these public or keep them private. If you want your module to keep state like an object with some private variables, it can. If you want to stash some functions in a namespace, you can use a module to do that.
Here's one way to do it:
var Module1 = function() {
var module = {};
var private1 = ... ;
module.public_function1 = function() {
...
}
module.PublicObject1 = function() {
...
}
var private_function1 = function() {
...
}
function PrivateObject1 {
...
}
...
return module;
}();
// Later...
var obj1 = new Module1.PublicObject1();
var result = Module1.public_function1();
// I'm adopting a rubyish convention of
// camel-casing classes (constructors in
// javascript speak) and underscoring other stuff.
// Whatever floats your boat.
- Module1 is a global variable which is assigned the
result of
function(){}()
- local variable 'module' refers to an empty object
(
{}
) which we proceed to fill - public functions and object constructors are added
to
module
; you could of course add variables tomodule
as well - private variables, functions and objects are
created without being added to
module
; they stay hidden within the scope ofModule1
closure module
is returned at the bottom to the outside world - in this case to the globalModule1
variable (GOTCHA #1)- Don't forget to execute
function(){...}()
otherwiseModule1
is just a refernce to the function itself (GOTCHA #2)
[6] The privileged function pattern
The term privileged comes from Douglas Crockford who clearly demonstrated it. The idea is that you can create private state in your object instances by using local variables inside the constructor.
A privilege function example was shown near the
beginning of this article using f where f = F()
.
The example below is one way to use this pattern
for instantiated javascript objects.
By adding a function to the this
variable inside
the constructor (or, in the example below I've
assigned this
to me
and used this instead),
this function, which acts like a public method, gets
access to these local variables which would otherwise
not be accessible (when you instantiate an object
instance). In the example below, accessor1
is an
example of a privileged function which can access
local variables priv1
and my
in the closure
formed by an instance of Object1
:
var Object1 = function() {
var me = this;
var priv1 = 'priv1';
var my = {};
// Private state. You don't have to do this.
// Just define functions and variables as normal
// if you prefer.
me.accessor1 = function() {
...
// accessor1 is a public method but can also
// access 'priv1' and 'my' above.
}
// You can also say 'this.accessor1 = ... '
my.init = function() {
...
}
my.init();
}
// Create an instance of Object1.
var obj = new Object1();
var result = obj.accessor1();
accessor1
can see everything inside the of function() that is referenced byObject1
.me
is a reference to the object (this
); there are potentially siuations wherethis
can change so we stash it inme
and useme
ever after; you can probably get by withthis
in many cases if you prefer- the use of
my
is not part of the pattern; it is something I often do to distinguish fromme
for private stuff that the outside world cannot see. Matter of taste. You could just use something like thepriv1
variable. - the
init()
function is not part of the pattern, but demonstrates defining and using a private function in this case to group all the initialisation instructions together to run at the end of the constructor when you instantiate an instance of the object.
Concluding Remarks
Judicious use of javascript's closures gives you surprising scope to build complex applications. Using the module pattern and privileged functions and closures in general you have plenty of scope to build complex behaviour within the browser and on the server using frameworks like rhino.
There's plenty of scope here to talk about conventions - how to organise and package code often using the above two patterns. I have written a standard that tries to set this out which I might revisit. You can find it here: http://js.web17.com.au/specs/packaging-and-namespacing/index.xhtml
You can find some of my javascript code at: http://github.com/danielbush
No comments:
Post a Comment