@main[] ^header[Unusual syntax] ^maindir[2]
^portrait[voloko]
Vladimir Kolesnikov ^email[voloko;]

Unusual syntax 19 March 2007


 
Task: Examine this: (item.isGood ? good : bad)["add" + (item.typeName || "Default")]((item.process || function(x){return x})(item.node))

The very basics 

"1234" is string 1234;
1.234 is value 1.234;
1.534E+4 is value 15340;
["aaa", "bbb", 21] is an array containing 3 elements: 2 strings and a value;
{aaa: "aaa", bbb:"bbb"} is an object with properties aaa and bbb;
[{foo: "bar", a: 12}, 25, ["hello"], new Date()] is an array in which the first element has foo and a properties, the second is value 25, the third is a one-element array (line "hello"), and the fourth is a Date object.

All javascript objects are reference type. Seeing a = { field: 12}; b = a; a and b.  Instead, both variables will refer to the same object. And a.field is equal to b.field.

var a = {field: 12};
var b = a;
b.field = 7;
alert(a.field);
output is 7. Similarly

var a = [1,2,3]
var b = a;
b[0] = 5;
alert(a[0]);
output is 5.
				

Basics 

You can access to object properties either using . (dot notation) or [] (brackets). If you write obj.value = 1 and obj["value"] = 1 the result and the speed will be exactly the same. Test.

It&6;s better not to use eval() for object referencing. The following line of code

var propertyName = "value";
eval("obj." + propertyName + "= 1")

takes 10—100 times longer to run than

obj[propertyName] = 1
				

Ternary operator allows you to make a simple code a single line. Expressions

if(a > b) {
	Obj.value = a;
} else {
	Obj.value = b;
}
				

and

Obj.value = a > b ? a : b;
				

are the same, but the latter is much shorter.

Ternary operator has very low priority, so
'hello ' + (2 == 2) ? 'world' : 'hell'
is just the same as
('hello ' + (2 == 2)) ? 'world' : 'hell'
and always returns &7;world &8;, not &7;hello world&8;.

Simultaneous assignments (/=, *=, -=, +=, %=, etc.) are sometimes used within other expressions. Assignment operator always returns the variable written before equality sign. Line a = b = 3 assigns value 3 to b , and since a equals b, it&6;s also 3. Operations
x = 5; y = x += 5
assign value 10 to x and y.

Operations can be used within more complicated expressions:
Obj.value = (x /= range) * x;
is identical to
x = x / range; Obj.value = x*x;

Operator OR (||) does not return Boolean value like it does in C. It returns either the first value different from false, or, in the absense of such value, the very last value. Therefore Obj.value = 0 || "hello" || 500 is "hello", not true.

And Obj.value = null || "" || false || 0 returns 0.

This is useful for setting defaul values:

function concat(str1, str2) {
	str2 = str2 || "default";
	return str1 + str2;
}
				

Within an expression OR performs the operation only if the previous values are false. So this code
Obj.value = true || myfunc()
will never call myfunc.

Operator AND (&&) returns either the first false value or the very last value.

It comes in handy when you need to perform operation provided that previos operations were completed successfully:
node && (tmp = node.getElementsByTagName('div')).length && tmp[0].innerHTML || ""
returns innerHTML of the first div element in the node, if it exists. If node does&6;t have child sites or does not itself exist, the output is an empty string.

Type transformation operations 

JavaScript doesn&6;t have strict rules. You can determine variable data type according to your purpose. Sometimes the type needs to be changed.

Loops 

A regular for block consists of three parts separated by semicolumns. The second part (condition) is performed for each iteration, so it should run fast. For example,
for(var i = 0, length = items.length; i < length; i++);
takes in average 3 times less time than
for(var i = 0; i < items.length; i++);
Test.

Each part can be any expression. It may call functions or define variables. The following line of code searches for the minimum value in an array:
for(var i = 1, min = items[0], length = items.length; i < length; min = Math.min(min, items[i]), i++);

You can also use for(var i in items){}. It goes through all indexes, properties and methods of items. For-in is convenient to use over all keys in the hash. It can be used for searching array indexes, but it would be highly unreasonable. Before running for-in the interpreter makes a list of all object keys. And then performs regular for. That&6;s why for-in is at a disadvantage (sometimes incomparably) with for. Test.

Beside, for-in goes through all properties on an object including the prototype. For example, well-known prototype.js uses bind method for all functions. And that means for-in will return key bind for each newly created with new obj() object. But in most cases you only need the object&6;s keys. Here is the solution:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
hasOwnProperty = function(obj, prop) {
        if (Object.prototype.hasOwnProperty) {
            return obj.hasOwnProperty(prop);
        }
        return typeof obj[prop] != 'undefined' && 
                obj.constructor.prototype[prop] !== obj[prop];
}
for(var i in obj) {
	if(!hasOwnProperty(obj, i)) continue;
	// some code
}

				

Functions 

There are two ways to write a function:
function a(x) { return 1 + x; }
or:
var a = function(x) { return 1 + x; }
(both are the same).

Each javascript function is an object, therefore you can perform the same operations with it. For example, you can assign it to a variable.In the second sample the function operator creates a new function object which is assigned to object a. You can as easily assign this function to another variable:
var b = a;
And then call that function:
b();

As any other object, a function may have properties. It is possible, for instance, to write:
b.someVar = 15;

You can also assign a function to an object property using either dot notation or square brackets:

var q = {};
q["someFunc"] = b;
q.someOtherFunc = function(){}
				

A function may be a return value. You can use this to create dynamic functions depending on the value:

function createReturn(val){
	if(val > 10) {
		return function() { return val; }
	} else {
		return function() { return 0; }
	}
}
var a = createReturn(12);
var b = createReturn(12); // here a and b refer to the same function, but a != b
				

It is not neccessary to have a function assigned. This code creates a function and calls it right after that:
var a = (function(i){ return 2 + i; })(10); //12

You can use logical operators with functions. Here is an example:
(funca || funcb)(12)
calls funca if it is defined and funcb if not.

Closures 

When you write a function, you can use any parent block variables. The next time you call this function, it will remember their values. For example:

01 
02 
03 
04 
05 
06 
07 
08 
function createSomething (a){
var b = 12;
function doSomething() {
	return a + b;
}
return doSomething;
}
( createSomething(10) )(); //22
				

Commentary

&7;Remembering&8; means that the interpreter creates references to the variables from the environment in which it was created. So if two functions were created in the same environment, thet will remember the same set of variables.


If you change values of the variables after the function was created, it will know it:

01 
02 
03 
04 
05 
06 
07 
08 
09 
function createSomething (a){
var b = 12;
function doSomething() {
	return a + b;
}
b *= b; // change b
return doSomething;
}
( createSomething(10) )(); //154
				

And here is a more complicated example:

01 
02 
03 
04 
05 
06 
07 
08 
var tmp = node.getElementsByTagName('li');
for(var i = 0; i < tmp.length; i++){
	tmp[i].onclick = function(num){
		return function() {
			alert(num);
		}
	}(i);
}
				

The code picks out all li from the node. Then it adds an onclick event handler to each element. The handler returns its element&6;s number.

You cannot do it by simply writing tmp[i].onclick = function(i){ alert(i); }.
Each element would return the last i value, which equals the number of elements in tmp. But you want the elements to return their own numbers.

The code can be a little easier to understand if there is a separate function for creating a handler and the alert:

01 
02 
03 
04 
05 
06 
07 
08 
09 
function createHanlder(num) {
	return function() {
		alert(num);
	}
}
var tmp = node.getElementsByTagName('li');
for(var i = 0; i < tmp.length; i++){
	tmp[i].onclick = createHandler(i);
}

				

Commentary

Using javascript closures along with DOM objects often causes Internet Explorer 5 and 6 to leak memory. You can read about in the article &7;IE: where&6;s my memory?&8;.


Answer 

Lets go back to the line in the task:
(item.isGood ? good : bad)["add" + (item.typeName || "Default")]((item.process || function(x){return x})(item.node))

Lets look at it out of context. It apparently uses three objects: good, bad and item. Object item has to have properties isGood, typeName and node, and process method. Objects good and bad have methods defines as add<typeName>.

There are three parts:

(item.isGood ? good : bad) // object to call a method on
["add" + (item.typeName ||  "Default")] // method to call
(item.process || function(x){return x})(item.node) // value passed to the method
				

So, that&6;s how it goes step by step:

A couple of examples:

Output for item.isGood = true, item.typeName = "String", item.process = null:
good["addString"](item.node)

Output for item.isGood = false, item.typeName = null, item.process = function(node){...}:
bad["addDefault"](item.process(item.node))
				

Worth it? 

The fact that such complex expressions exist does not mean you have to use them. This line sample shows what you shouldn&6;t do. It is hard to solve &7;mysteries&8; of the kind, but it helps to know about them. At least, when you read scripts written by someone else.

Wise examples of use can be found in the code library prototype.js.

More 

I meant to avoid classes (and prototypes). This would make another article.

It is likely that I missed some common expressions. I&6;d appreciate it if you

^include[/bottomblok.html]