Professional JavaScript | 11 | WebReference

Professional JavaScript | 11


Professional JavaScript

Turning Script Statements Into Data

Bits of JavaScript can be very simply turned back into data if you choose the right bits. Here are a couple of examples:

var greatgranddad = new Patriach;
greatgranddad.child1 = new Granddad;
// ...
// lots more family tree detail
// ...
var tree=greatgranddad.toString();  // If you specify JavaScript1.2

In this example, a hierarchy of objects is created starting with Great-Granddad who clearly is the most senior. When the toString() method is called, the object is forced into a String type, and if you're lucky (because the different interpreter brands are a little varied in this functionality still) what will be printed to the screen will be a literal object matching the object's contents. In this case the literal would start:

{ child1:{ …

Note, however, that this functionality of toString() is only available if you specify LANGUAGE = "JavaScript1.2" when writing it. In JavaScript 1.3 and beyond (NC4.06+, IE5+), this functionality is taken on by toSource().

A similar trick is possible with functions:

function first() { … }
function second() { … }
function third() { … }
function do_it()
  first() || throw first.toString();
  second() || throw second.toString();
  third() || throw third.toString();
try { do_it(); }
catch ( e ) { alert('failed: ' + e) }

In this example, you might be testing 3 functions for syntax problems. If any of them fail, returning false, their source code is returned as an exception, and the user (presumably) is shown the offending code via the alert() function in the catch block. Recall Microsoft's JScript 5.0 is the only implemented contender at this time of writing for try and catch statements.

In both these cases, the toString()/toSource() method is not always required – recall from Chapter 1 that there is automatic type conversion in JavaScript.

This process won't necessarily work everywhere. For example, JavaScript statements that aren't part of any function are harder to get back as data. The JavaScript interpreter in Internet Explorer 4.0 is a notable exception.

The Mighty eval()

The native function eval() is by far the most powerful native function in JavaScript, allowing a JavaScript string to be examined, interpreted and run as though it was a script.

var x=5, y=6, result=0;
var formula = "result = x*y;";
eval(formula);                     // result is now 30

The contents of the formula variable can be as complicated as you like – multiple statements, if-else logic, and so on. Provided the string contains valid JavaScript, everything works fine. However, if the string contains invalid JavaScript, the interpreter will give a runtime error and halt. Because the script fragment is hidden inside a string, the interpreter has no chance of detecting problems with it when it first reads the whole script. That means that syntax checks that would otherwise be performed at script startup time are delayed until the eval() statement is actually executed. That means you must take extreme care when using eval(), or else everything can come crashing down much later on, when you least expect it. Perhaps by the time this book is in your hands there will exist an interpreter that issues an exception instead.

Here's a more complicated example of eval(). This example runs a different function depending on whether the boss is around or not:

var type1 = "function work() { slog(); slog(); slog(); }";
var type2 = "function work() { rest(); rest(); rest(); }";
if ( boss ) 

Notice how you can delay defining any kind of work at all until the boss is around. Handy.

eval() has the potential to be used pretty powerfully. Suppose you have an application that hosts a JavaScript interpreter. Suppose that application connects to another application, possibly on a remote computer that also hosts a JavaScript interpreter. A script in the local application could turn some script functions back into data using toSource(), and then send them to the remote application via a convenient host object. The remote application could then eval() the data, turning it back into scripts. This kind of architecture allows scripts to move around a computer network and execute in different places as "mobile agents", a very modern and fluid approach.

Basically this technique works anywhere you can store interpretable code in a string and execute it. Macintosh users may recognize that HyperCard does the same with the do command. In a Bourne shell window, you can write text out to a file with an echo statement, chmod it to make it executable and 'dot run' it into the current process space to achieve the same goal.

Piggyback embedding

Let's look at the way an eval() may help us out with some interesting problems.

Within the context of distributed JavaScript, you can put a function in a string, send the string to a script on another computer which then does an eval(), executes it, gets an answer, and passes the answer, the function, or a modified version of the function back to the sending computer, or possibly to another computer. So facilities that only really exist on one computer can be executed on request from a remote master machine.

The piggybacking comes from the way you carry one script around in another. Technically there is no limit to the number of enclosures within enclosures. The scripts can transcend languages and be embedded in completely foreign environments. For example, you could transfer a Bourne shell script inside a JavaScript script that was transferred across the network from a series of strings that were constructed in a source script written in Perl.

To construct these mechanisms you basically work backwards from the ultimate target environment, wrapping the scripts with increasing numbers of onion skin layers as you go.

Created: February 12, 2001
Revised: February 16, 2001