RND template redux

I rebuilt my benchmark from last since it had some flaws. The new one features:
  • 50x50 table
  • Top rows and left-most columns will be filled with some fixed text (some of them)
  • The other 2401 cells will be populated with random numbers
  • The fixed names will be extracted from a JSON object
  • AJS.RCN isn't used for basic DOM
  • A basic event added
  • The text will be decorated in bold and span

I also think that some people have misunderstood RND. It isn't designed to fully replace DOM or innerHTML [they are both good for their things], it's meant to complement them. I.e. RND is a new tool for your web toolbox.

New RND version

This new version features:

  • eval isn't used, actually, you can pass what scope to look at. window is the default scope.
  • A bug fix (-1 or 0 wasn't displayed)
function RND(tmpl, ns, scope) {
  scope = scope || window;
  var fn = function(w, g) {
    g = g.split("|");
    var cnt = ns[g[0]];
    for(var i=1; i < g.length; i++)
      cnt = scope[g[i]](cnt);
    if(cnt == 0 || cnt == -1)
      cnt += '';
    return cnt || w;
  };
  return tmpl.replace(/%\(([A-Za-z0-9_|.])\)/g, fn);
}

What can it do now?

Here is a new example where you define the scope:

var tmpl = '<a href="%(link)">%(value|timesTwo)</a>';
var name_space = {link: 'http://amix.dk', value: 5.5};
var myfn = {
  timesTwo: function(v) {
    return v*2;
  }
}
alert(RND(tmpl, name_space, myfn));

This will alert:

<a href="http://amix.dk">11</a>

Notice that the value has been multiplied with 2.

Here are some ideas for filters:

  • Captialize text
  • Truncate text to 20 chars
  • Date format

The new test code

Here is how the code that builds the test table looks like. You be the judge of fairness and style.

Basic DOM

var span, div, img;
for(var i=0; i < iterations; i++) {
//Delete container in a fair way
cnt.innerHTML = "";

var x = document.createElement('table');
x.border = 1;
var y = document.createElement('tbody');
x.appendChild(y);

var z, span, txt, td, b;
for (var i=0; i < row_col_count; i++) {
  z = document.createElement('tr');
  y.appendChild(z);
  z.onmouseover = colorCell;
  for (var j=0; j < row_col_count; j++) {
    b = document.createElement('b');
    span = document.createElement('span');
    span.style.color = "red";
    txt = document.createTextNode(getText(i, j));

    span.appendChild(txt);
    b.appendChild(span);

    td = document.createElement('td');
    td.appendChild(b);
    z.appendChild(td);
  }
}

cnt.appendChild(x)
}

AJS DOM

var new_elm;
for(var i=0; i < iterations; i++) {
  //Delete container in a fair way
  cnt.innerHTML = "";

  var y = AJS.TBODY();
  var x = AJS.TABLE({border: 1}, y);

  var z;
  for (var i=0; i < row_col_count; i++) {
    var z = AJS.TR();
    z.onmouseover = colorCell;
    AJS.ACN(y, z);
    for (var j=0; j < row_col_count; j++)
      AJS.ACN(z, 
        AJS.TD(
          AJS.B(
            AJS.SPAN({style: 'color: red'}, getText(i, j)+''))));
  }
  cnt.appendChild(x)
}

innerHTML

for(var i=0; i < iterations; i++) {
var html = '<table border="1">';
var tr;
for (var i=0; i < row_col_count; i++) {
  tr = '<tr>';
  for (var j=0; j < row_col_count; j++) {
    tr += '<td onmouseover="colorCell(this)"><b>';
    tr += '<span style="color: red">' + getText(i, j) + '</span>';
    tr += '</b></td>';
  }
  html += tr + "</tr>";
}
html += "</table>";

cnt.innerHTML = html;
}

RND template

var tmpl_table = '<table border="1">%(rows)</table>';
var tmpl_col = AJS.join('', [
    '<td onmouseover="colorCell(this)">',
      '<b><span style="color: red">%(cnt)</span></b>',
    '</td>']);

for(var i=0; i < iterations; i++) {
  var rows = '';
  for (var i=0; i < row_col_count; i++) {
    var cells = '';
    for (var j=0; j < row_col_count; j++)
      cells += RND(tmpl_col, {cnt: getText(i, j)});
    rows += '<tr>' + cells + '</tr>';
  }
  cnt.innerHTML = RND(tmpl_table, {rows: rows});
}

The new results

Internet Explorer 6.0.2900

Time run: 10
Number of iterations: 10
Basic DOM: 9124 ms
AJS DOM: 11809 ms
innerHTML: 2101 ms
RND template: 2335 ms

FireFox 1.5.0.6

Time run: 10
Number of iterations: 10
Basic DOM: 1509 ms
AJS DOM: 4330 ms
innerHTML: 777 ms
RND template: 1085 ms

Opera 9

Time run: 10
Number of iterations: 10
Basic DOM: 912 ms
AJS DOM: 1394 ms
innerHTML: 619 ms
RND template: 1234 ms

Conclusion

innerHTML is once again the fastest method in all browsers. RND is slightly faster than basic DOM. AJS DOM is the slowest, but most powerful method.

In the future I am building very complex user interfaces using innerHTML (through RND) [of course, with a mix in of DOM where needed].

A little notice for IE: innerHTML and RND was around 45 times faster than basic DOM for a test case where one does not use bold and span.

Download the new suite

Code · Code improvement · JavaScript 13. Aug 2006
18 comments so far

amix, nice scope representation of a map of functions:

var myfn = {
  timesTwo: function(v) {
    return v*2;
  }



BTW: the unescaped parentheses crept back in (does your source document somehow not need escaping?)

Hi Brendan

I have now fixed the parenteses bug, thanks for the notification.

I love the new example! Much closer to real life. My official analysis is that RND is a nice way to create templated javascript quickly and cleanly, though when it comes to speed I'd still prefer the DOM.

Since I was curious, I optimized the basic DOM a bit. My reasoning? Well you're re-creating DOM nodes all over the place, when you could really be cloning one element.

The TD with the B and the SPAN in it is always the same, why keep re-creating it?

I have updated my blog with my version of the basic DOM test, done out of curiosity.

To be honest I didn't think that optimizing the DOM would have any appreciatable difference against your RND. String concat vs object manipulation? I was surprised to say the least.

Hi Joe

Again very interesting work you have done. Thanks for the time you put in this.

Cloning B and SPAN nodes would have made the code even more obfuscated :( I was also interested in finding out how long it took to actually create a new DOM object (and as it seems by your research, there is an overhead, but it's very little, but enough to give it an edge :]). Anyway, cloning is a good optimization to have in mind, but I don't think it's normal practice.

Thought, one should take into account that innerHTML and RND is a lot faster than DOM for the most used browser out there (i.e. IE)

Anyway, this research was interesting.

That's absolutely true. RND and innerHTML is a lot faster in IE. It is also true that your solution is also less verbose than my DOM example.

At the same time though, innerHTML was created for the browser in which it is the fastest. It is not a standard. However, the Document Object Model is a published standard.

RND stays reliant on innerHTML and that's one of the reasons I'd shy away from using it.

My final reason for not liking innerHTML is that it leads to confusion.

I offer this piece of code:

var one = document.createElement('div');
var two = document.createElement('span');
one.appendChild(two);
one.innerHTML = '';

Most people would look at this code and think, ok, you're creating two elements, putting them together, then deleting the inner element.

That isn't the case at all. The element still exists in the variable 'two'.

This may seem straight forward in this example, but what if the reference to the element isn't in a variable as obviously as it is here?

What if there are references in JavaScript code earlier in the page? Even worse, what if there are references stored in attributes of other objects on the page? In that case, using innerHTML makes the code a little more prone to unintentional memory leaks, as the object won't be garbage collected until it isn't referenced anymore.

In the end it comes straight down to personal preference.

I really appreciate your research in all of this as it has helped clarify many aspects of performance of different browsers and the Document Object Model.

Hi Joe

Again a very constructive comment, thanks for this.

It's true that innerHTML is a non-standard, but basically all browsers implement it, so it's some kind of a standard. And I am pretty sure they wont remove it :)

Your example is legitimate, but using DOM deletion could also lead to confusion and memory leaks. An example, replacing the last line with this:

one.removeChild(two);


You will still have a reference to variable "two" and have the same problems.

Another thing, that I think could be very interesting to research is Internet Exploders rendering engines - - I think one would find very interesting results. For me it looks like they have bolted DOM support. Basically, one should test basic DOM in the quirks mode (i.e. hackerish HTML), then do the same test where XHTML is used.

I don't have access to a Windows PC until next weekend, so I can't do these tests myself. You are very welcome to do them if you run Windows.

Btw. Joe, your josephbauser.net is really cool. Well done.

Thank you very much for the compliment! I'm still working on that site. Hopefully I'll have enough free time to flesh out the UI and JavaScript concernes I have relatively soon.

I completely agree that the DOM version has the same issue. However the fact that you require a reference to remove a DOM element, in my mind at least, brings that reference to the front of your mind.

The poking at the inner workings of Internet Explorer does sound like fun, and I may just test a few things myself in the future. If I do, I'll make sure to drop a line here. I can always use another pair of eyes to help out. :)

I am quite curious if there are any appreciatable differences between DOM manipulation in quirks mode and DOM manipulation in standards compliant mode. Hopefully I'll have some spare time to test it out relatively soon.

Just out of curiosity, have you ever run into the innerHTML DOM update bug? I managed to reproduce it once but I haven't run into it since.

It has to do with the population of portions of the DOM using innerHTML which uses events.

Then the events themselves access the DOM nodes which are a part of the page populated with an assignment to innerHTML. Due to a problem in the threading model in some browsers (or so it was described to me) the modifications to innerHTML appear on the page and are visible before the modifications are actually made to the DOM tree. Because of this the events (which can be activated as the nodes are on the page) receive undefined references if they access the DOM itself.

It really has been a pleasure. Again good job with RND, I love how much it can do with such a small footprint. :)

"Just out of curiosity, have you ever run into the innerHTML DOM update bug?"

No, I haven't. Actually, I didn't use innerHTML before. I mostly used AJS DOM since I find it quite convenient.
And I don't plan to use innerHTML for elements that require events, so hopefully i wont meet it :)

Anyway, i'll keep an eye on your blog.

P4 3GHz, 1GB

IE7 beta 3
Time run: 10
Number of iterations: 10
Basic DOM: 2800 ms
AJS DOM: 3483 ms
innerHTML: 377 ms
RND template: 568 ms

FF 1.5.0.6
Time run: 10
Number of iterations: 10
Basic DOM: 918 ms
AJS DOM: 1938 ms
innerHTML: 425 ms
RND template: 601 ms

Opera 9.01
Time run: 10
Number of iterations: 10
Basic DOM: 377 ms
AJS DOM: 843 ms
innerHTML: 240 ms
RND template: 669 ms

Jeebus, is Opera really that fast?

Core Duo 1.83 GHz, 512 MB

IE6
Time run: 10
Number of iterations: 10
Basic DOM: 2220 ms
AJS DOM: 3290 ms
innerHTML: 332 ms
RND template: 409 ms

FF 1506
Time run: 10
Number of iterations: 10
Basic DOM: 779 ms
AJS DOM: 1790 ms
innerHTML: 315 ms
RND template: 532 ms

Completely OT:
This test only utilizes one core. Is this any indication as to how bad a line of procs the P4 was? =-)

Great work. I am using jquery and RND save me a lot of time.
Bravo majstore!

Just to save anyone else the trouble of debugging (this one nearly killed me) I'll point out that the code displayed above is missing a '+' after the closing square bracked in the replacement regex.

Glad I nailed that one.

WOW!

Script works, I'm happy!

I have wrapped this handy little snippet in a class and added the ability to pass multiple fields to a function. I am more or less using this server-side to drive a template engine. Just thought I'd post a modification here for everyone that allows conditionals in your template.

function RND(tmpl, ns, scope) {
	scope = scope || window;
	
	var output = '';
	
	var rxFIELD = /%\(([A-Za-z0-9,_|.]*)\)/g;
	var rxIF = /@IF\(([A-Za-z0-9,_|.]*)\)((.|\n)*?)@END/g;
	
	var fnFIELD = (function(w, g) {
		g = g.split('|');
		var cnt = ns[g[0]];
		for(var i = 1; i < g.length; i++) cnt = scope[g[i]](cnt);
		if(cnt === 0 | cnt === -1) cnt += '';
		return cnt || w;
	}).bind(this);
	
	var fnIF = (function(w, g, txt) {
		g = g.split('|');
		var cnt = ns[g[0]];
		var result = true;
		if(g.length == 1) result = result && cnt;
		for(var i = 1; i < g.length; i++) result = scope[g[i]](cnt);
		return result ? txt : '';
	}).bind(this);
	
	output = tmpl.replace(rxFIELD,fnFIELD);
	output = output.replace(rxIF,fnIF);
	return output;
}

Basically, you use RND just as before, only now you can have expressions like:

@IF(boolValue)<h4>Something Here</h4>@END

"Something Here" will only be displayed if boolValue is true.

Similarly, you can have:

@IF(someValue|checkCond)<h4>Something Here</h4>@END

"Something Here" will only be displayed if checkCond returns true. someValue will be passed to it as a parameter. You can separate multiple functions with a pipe, just as you can with the original RND, except that each must evaluate to a boolean and only if all of them are true will the text be displayed.

You cannot nest @IF statements. I separated the regular expressions so you can change them if you would rather use something like %IF and %ENDIF.

Woops! Forget about my bind(this) in the above code. That's an artifact from refactoring it from my class. It should read:

function RND(tmpl, ns, scope) {
	scope = scope || window;
	
	var output = '';
	
	var rxFIELD = /%\(([A-Za-z0-9,_|.]*)\)/g;
	var rxIF = /@IF\(([A-Za-z0-9,_|.]*)\)((.|\n)*?)@END/g;
	
	var fnFIELD = function(w, g) {
		g = g.split('|');
		var cnt = ns[g[0]];
		for(var i = 1; i < g.length; i++) cnt = scope[g[i]](cnt);
		if(cnt === 0 | cnt === -1) cnt += '';
		return cnt || w;
	};
	
	var fnIF = function(w, g, txt) {
		g = g.split('|');
		var cnt = ns[g[0]];
		var result = true;
		if(g.length == 1) result = result && cnt;
		for(var i = 1; i < g.length; i++) result = scope[g[i]](cnt);
		return result ? txt : '';
	};
	
	output = tmpl.replace(rxFIELD,fnFIELD);
	output = output.replace(rxIF,fnIF);
	return output;
}

Hi..... i am using YUI for developing drag and drop ....i have a problem with assigning innerHTML property.... i am developing a drag nd drop functionality.. the following is the structure of the document:

1) parent_DIV
2) child_DIV_1 and child_DIV_2

The drag and drop functionality works fine... the "child_DIV_1" has a icon to mimimize/maximize "child_DIV_2"... but after drag and drop the new location "child_DIV_1" still performs the action on OLD "child_DIV_1".... this is because i am directly assigning the oldParent's innerHTML to new one.... how do i solve this problem??

I HAVE USED THE cloneNode().. THE BUG STILL PERSISTS

Post a comment
Commenting on this post has expired.
© 2000-2009 amix. Powered by Skeletonz.