[jQuery] For Loops vs Each Loops while optimizing efficiency

Discussion in 'jQuery' started by curious_explorer, Aug 2, 2016.

  1. #1
    I have a trac plugin named DynamicFieldsPlugin. My studies show that this piece of code takes the most time. I know there are a lot of each used here. So I need your advice to improve its performance.
    Here is my layout.js code:
    /*
    * Layout 'class'
    */
    var Layout = function(name){
        this.name = name;
       
        // Selector for all field tds/ths
        this.selector = '';
    
        // Return the given field name's td/th element
        this.get_tx = function(field){}
       
        // Return the given td/th element's field name
        this.get_field = function(tx){}
       
        // Move a field's tds and ths to slot i
        this.move_field = function(field, i){}
       
        // Update the field layout given a spec
        this.update = function(spec){
            var this_ = this;
           
            // save original field order
            if (window.dynfields_orig_field_order == undefined)
                window.dynfields_orig_field_order = Object();
           
            if (window.dynfields_orig_field_order[this.name] == undefined){
                window.dynfields_orig_field_order[this.name] = [];
                jQuery(this.selector).each(function(i,e){
                    var field = this_.get_field($(this));
                    if (field)
                        window.dynfields_orig_field_order[this_.name].push(field);
                });
            }
           
            // get visible and hidden fields
            var visible = [];
            var hidden = [];
            jQuery.each(window.dynfields_orig_field_order[this.name],
                        function(i,field){
                var tx = this_.get_tx(field);
                if (tx.hasClass('dynfields-hide')){
                    hidden.push(field);
                } else {
                    visible.push(field);
                }
            });
           
            // get new field order
            // warning: side-effects!
            var new_fields = jQuery.merge(visible, hidden);
           
            // order the fields
            this.order_fields(new_fields);
        }
       
        this.order_fields = function(new_fields){
            var this_ = this;
           
            // determine which fields need to move and move 'em!
            jQuery(this.selector).each(function(i,e){
                var field = this_.get_field($(this));
                if (field == ''){
                    var j = -1;
                } else {
                    var j = jQuery.inArray(field, new_fields);
                }
               
                // check if field is in the correct slot in the new order
                if (i != j && i < new_fields.length){
                    // wrong order!
                    this_.move_field(new_fields[i], i);
                }
            });
        }
    };
    
    
    /*
    * Inputs Layout implementation
    */
    var inputs_layout = new Layout('inputs');
    
    // selector
    inputs_layout.selector = '#properties td[class!=fullrow]:parent';
    
    // get_tx
    inputs_layout.get_tx = function(field){
        return jQuery('#field-'+field).parents('td:first');
    };
    
    // get_field
    inputs_layout.get_field = function(td){
        var input = td.find(':input:first');
        if (!input.length) return '';
        return input.attr('id').slice(6);
    };
    
    // move_field
    inputs_layout.move_field = function(field, i){
        var td = this.get_tx(field);
        var th = td.parent('tr')
                   .find('th label[for=field-'+field+']')
                   .parent('th');
       
        // find correct row (tr) to insert field
        var row = Math.round(i/2 - 0.5); // round down
        row += jQuery('#properties td[class=fullrow]').length; // skip fullrows
        var tr = jQuery('#properties tr:eq('+row+')');
       
        // find correct column (tx) to insert field
        var col = 'col'+((i%2)+1);
        if (tr.find('th').length){
            if (col == 'col1'){
                var old_th = tr.find('th:first');
                if (old_th.get(0) != th.get(0)){ // don't move self to self
                    old_th.before(th);
                    old_th.before(td);
                }
            } else {
                var old_td = tr.find('td:last');
                if (old_td.get(0) != td.get(0)){ // don't move self to self
                    old_td.after(td);
                    old_td.after(th);
                }
            }
        } else {
            // no columns so just insert
            tr.append(th);
            tr.append(td);
        }
       
        // let's set col
        td.removeClass('col1 col2');
        th.removeClass('col1 col2');
        td.addClass(col);
        th.addClass(col);
    };
    
    
    /*
    * Header Layout implementation
    */
    var header_layout = new Layout('header');
    
    // selector
    header_layout.selector = '#ticket .properties th:parent';
    
    // get_tx
    header_layout.get_tx = function(field){
        return jQuery('#h_'+field);
    };
    
    // get_field
    header_layout.get_field = function(th){
        return th.attr('id').slice(2);
    };
    
    // move_field
    header_layout.move_field = function(field, i){
        var th = this.get_tx(field);
        var td = th.parent('tr').find('td[headers=h_'+field+']');
       
        // find correct row (tr) to insert field
        var row = Math.round(i/2 - 0.5); // round down
        var tr = jQuery('#ticket .properties tr:eq('+row+')');
       
        // find correct column (tx) to insert field
        if (tr.find('th').length){
            if (i % 2 == 0){
                var old_th = tr.find('th:first');
                if (old_th.get(0) != th.get(0)){ // don't move self to self
                    old_th.before(th);
                    old_th.before(td);
                }
            } else {
                var old_td = tr.find('td:last');
                if (old_td.get(0) != td.get(0)){ // don't move self to self
                    old_td.after(td);
                    old_td.after(th);
                }
            }
        } else {
            // no columns so just insert
            tr.append(th);
            tr.append(td);
        }
    };
    Code (JavaScript):
    I will happily give more information about this issue. Please help.
     

    Attached Files:

    Solved! View solution.
    curious_explorer, Aug 2, 2016 IP
  2. #2
    The .each() is being used only three times in your code so there are not "a lot of" .each() used here.

    According the responses made in the following stackoverflow thread, the difference between .each() and for() loop is noticeable only when the iteration is tens of thousand times. If the iteration of your .each() do not need to be performed tens of thousand times, I would say it's not a problem.
    http://stackoverflow.com/questions/11887450/each-vs-for-loop-and-performance

    One way to improve the speed is always performing trivial calculations outside of .each(). For example, rather than writing code like if (i < $('div').length) { ...... } inside of .each(), do var divLength = $('div').length; outside of .each() and write the code inside of .each() as if (i < divLength) { ...... }.
     
    Last edited: Aug 7, 2016
    Ian08, Aug 7, 2016 IP