views:

117

answers:

3

I need to make this jquery run much faster, I did not create all of these functions, but need their methods, to do live translations of page content.

So I would appreciate any suggestions to refactor for better performance, the code below.


/* function 1 read json into array */
/* function 2 loop through array */
/* function 3 replace text nodes based on data from looped array */

var en_lang = new Array();
var fr_lang = new Array();
var frca_lang = new Array();
var my_data = null;
var en_count = 1;
var fr_count = 1;
var frca_count = 1;

if(typeof(language) != "undefined"){
    var language = 'frca';
}

function replaceText(oldText, newText, node){ 
  node = node || document.body; // base node 

  var childs = node.childNodes, i = 0;

  while(node = childs[i]){ 
    if (node.nodeType == 3){ // text node found, do the replacement
      if (node.textContent) {
        node.textContent = node.textContent.replace(oldText, newText);
      } else { // support to IE
        node.nodeValue = node.nodeValue.replace(oldText, newText);
      }
    } else { // not a text mode, look forward
      replaceText(oldText, newText, node); 
    } 
    i++; 
  } 
}
function parsejSON(my_data) {
    /* THIS PART GRABS DATA FROM TOP OF JSON FILE */

    /* grab recordcount */
    var recordcount = my_data.recordcount;
    /* grab columnlist */
    var columnlist = my_data.columnlist;
    /* grab json data */
    var json_data = my_data.data;

    /* PUTS JSON DATA INTO ARRAYS */
    for(var x = 0; x < recordcount; x++) {
        var lng = json_data.lng[x];
        var phrase = json_data.phrase[x];

        if (lng == 'french') {

            fr_lang[fr_count] = phrase;
            fr_count = fr_count + 1;
        }
        if (lng == 'french canadian') {

            frca_lang[frca_count] = phrase;
            frca_count = frca_count + 1;
        }
        if (lng == 'english') {

            en_lang[en_count] = phrase;
            en_count = en_count + 1;
        }
    }

    /* use a replacetext function above to replace all text */ 

    for(var x = 0; x < en_lang.length; x++) {
      var from = en_lang[x];
      if (language == 'fr') {  
        var to = fr_lang[x];
      }
      if (language == 'frca') {  
        var to = frca_lang[x];
      }
      replaceText(from, to);
    }



}

Sorry, not quite sure how to get the code posting, formatted correctly, feel free to edit my post in that situation..

Thank you.

A: 

You could try console.time in firebug. That will tell you how much time you save when you make changes. It will also help you to figure out which of the functions takes the longest to execute (so you can tackle optimizing them first.)

http://stackoverflow.com/questions/313893/javascript-how-to-measure-time-taken-by-a-function-to-execute/1975103#1975103

Tim Snowhite
+2  A: 

By examining your code I can tell you that your most overhead is created in replaceText function. For every text item, your code walks again and again through the whole DOM structure. Instead of doing this in one go, code goes through document en_lang.length numbers of times. Node manipulation is much slower then JavaScript code, so if you have a lot of items in your en_lang array and document structure is complex the rendering time can grow drastically. By doing the replacement of all text items in one loop would save a lot of time.

Second. I can see, that on every node you perform assigning operation. node.textContent = node.textContent.replace(oldText, newText); This operation is overkill if your node does not contain any match information. By doing DOM assignment only where it's needed, you save yourself another ms of rendering time.

Third. Recursion. Really interesting topic. Can be used, can be not used. What you prefer. But sometimes recursion can be a bottleneck in a code. In this situation replaceText is an recusrive operation, which leads to unneeded memory ussage and CPU time. You can aquire all of the nodes in one step by asking it with getElementsByTagName function.

Fourth one : "if" operations inside "parseJson" in first for loop. I do not know why, but you do not use if-else statements, but simple if. This means that runtime should go through all of the "if" statements even if it has already found the correct one. In this case for JavaScript efficiency, it's better to use "switch" statement instead of "if" or "if-else". It's faster.

Fifth. Let's look now in the second "for" loop. You should not use there any switch or if statement at all. The "if" statements in that part are complete overkill. You know the value of your "language" variable before for loop, so you can cache the "to" array before the for loop and use already cached array.

Six : Another small improevement, instead of using frca_count = frca_count + 1; you can just use incremental operator: frca_count++. It will also speedup the code.

Seven. In your second "for: statement you use en_lang.length in conditional part. If you look at the previous "for" loop you can see that it has the variable which holds the same valu : "en_count". Use it instead of en_lang.length to speed up for loop itself.

Combining this all together, I came up with something like this.

function parsejSON(my_data) {
 /* THIS PART GRABS DATA FROM TOP OF JSON FILE */

 /* grab recordcount */
 var recordcount = my_data.recordcount;
 /* grab columnlist */
 var columnlist  = my_data.columnlist;
 /* grab json data */
 var json_data   = my_data.data;

 /* PUTS JSON DATA INTO ARRAYS */
 for(var x = 0; x < recordcount; x++) {
  var lng    = json_data.lng[x];
  var phrase = json_data.phrase[x];

 // ( conclusion # 4)
  switch(lng){
   case 'french':
    // ( conclusion # 6)
    fr_lang[fr_count++] = phrase;
    break;
   case 'french canadian':
    frca_lang[frca_count++] = phrase;
    break;
   case 'english':
    en_lang[en_count++] = phrase;
    break;
  }
 }

 /* use a replacetext function above to replace all text */ 
 // ( conclusion # 5)
 var toCache = (language == 'fr') ? fr_lang 
 : (language  == 'frca' ? frca_lang : null);


 // ( conclusion # x)
 var content = function(node, txt){
  if(txt){
   if(node.textContent){
    node.textContent = txt;
   }else if(node.nodeValue){
    node.nodeValue = txt;
   }
  }else{
   return node.textContent ? node.textContent : node.nodeValue;
  }
 };

 // recursive tree walker
 (function(parent){
     var childs = parent.childNodes;
     if(childs && childs.length){
         for(var i=0,node;node = childs[i];i++){
             if(node.nodeType == 3){ // text node found, do the replacement
                 var value  = content(node);
                 // ( conclusion # 7)
                 for(var x = 0; x < en_count; x++) {
                     var from = en_lang[x];
                     var to   = toCache[x];
                     // ( conclusion # 2)
                    if(value.match(from)){
                        content(node, value.replace(from,to));
                    }
                 }
             }else{
                 arguments.callee(node);
             }
         }
     }
 })(document.body);

}

All that should do the trick, I hope. Good luck

EDITED: Because getElementsByTagName("*") does not return #text nodes, I've changed solution to use recursive tree walker.

nemisj
I get an error childs not defined, for that while loop under conclusion #1
crosenblum
I fixed the error, and no more errors, but it just does nothing.
crosenblum
I've changed my answer, completely forgot that getElementsByTagName does not return text nodes. Try it now.
nemisj
Awesome and it is wicked fast.
crosenblum
Now our problem remaining is cross-browser compatibility, lol. It works perfectly in firefox, but does not in google chrome. Any suggestions?
crosenblum
I've tested in in Chrome with some example data, and it worked fine. So it's difficult to predict where error can be.
nemisj
A: 

I would suggest putting each localized param into a span tag, then you can select and update against your parameters a bit easier. Having a .localize classname, allong with a .l-PARMNAME classname will have a fairly tight selectability for this.

ex: <span class="localize l-PARMNAME">English Value</span>

you can then have your data with...

jsonData: { //note: this can come back from your ajax call
  language:'fr', 
  parms: { 
    "PARMNAME": "parm value",
    "OTHERPARM": "other value",
    ...
  }
}


$.each(jsonData.parms, function(key, value){
  ($('span.localize.l-'+key).text(value);
});

The above code should run a bit faster (using jquery) in more recent browsers, that have a better selection model. It should run okay in pretty much any browser though. If performance is still an issue, you could do a loop through $("span.localize").each() and check the appropriate sub-tag.

Otherwise, you could simply loop against your english version, but I would suggest using a span tag for each replicable node. even just the "span.localize" selector would give you a smaller node, you can compare the .text() value against, and match/replace the entire string... using a .replace on the string within a text node is fairly heavy, since you have to get the value of the text, match the string, (regex based), then input the new value into the text node. being able to have a narrow tag selector, and from there be able to specifically target the node you want will work with improved dom searching in newer browsers.

Tracker1
Not possible, with this version of our code. We just have 100's of pages to modify, to make server-side file changes. Which is why we must rely on existing code but make client-side changes.
crosenblum