ResourceLoader/Default modules/compareObject test

From MediaWiki.org
Jump to: navigation, search

In /resources/mediawiki/mediawiki.js a jQuery prototype was added by Krinkle to compare objects for equalness by iterating over the properties and comparing values. most of it is pretty straight forward, except for one thing and that is: How to check if there are no extra properties in ObjectB that do not exist in ObjectA ?

There were two concepts:

  • Keep a A_size variable and increment it by 1 throughout the loop of properties of ObjectA
    then after the loop do another loop through ObjectB's properties and keep a B_size variable and compare the two size-variables afterwards
  • After the loop of ObjectA's properties do another loop of ObjectB's properties and check if it is in ObjectA, if not return false.


[edit] Script

Following is a script that defines both functions (compareObject_count() and compareObject_forIn) and defines a few more functions to mass-execute test cases. The results are included at the bottom.

/**
 * The COUNT-method
 */
compareObject_count = function ( objectA, objectB ) {
 
        // Do a simple check if the types match
        if ( typeof( objectA ) == typeof( objectB ) ) {
 
                // Only loop over the contents if it really is an object
                if ( typeof( objectA ) == 'object' ) {
                        // If they are aliases of the same object (ie. mw and mediaWiki) return now
                        if ( objectA === objectB ) {
                                return true;
                        } else {
                                // Keep track of the size so we can check the number of properties afterwards
                                var A_size = 0;
                                // Iterate over each property
                                for ( var prop in objectA ) {
                                        // Check if this property is also present in the other object
                                        if ( prop in objectB ) {
                                                // Compare the types of the properties
                                                var type = typeof( objectA[prop] );
                                                if ( type == typeof( objectB[prop] ) ) {
                                                        // Recursively check objects inside this one
                                                        switch ( type ) {
                                                                case 'object' :
                                                                        if ( !compareObject_count( objectA[prop], objectB[prop] ) ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                                case 'function' :
                                                                        if ( objectA[prop].toString() !== objectB[prop].toString() ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                                default:
                                                                        if ( objectA[prop] !== objectB[prop] ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                        }
                                                        A_size++;
                                                } else {
                                                        return false;
                                                }
                                        } else {
                                                return false;
                                        }
                                }
                                // Calculate B's size
                                var B_size = 0;
                                for ( var prop in objectB ) {
                                        B_size++;
                                }
                                if ( A_size != B_size ) {
                                        return false;
                                }
                        }
                }
        } else {
                return false;
        }
        return true;
}
 
function runTest_count(){
        // the same
        var a = compareObject_count( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        } );
        // different
        var b = compareObject_count( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 510
        } );
        // extra stuff in B
        var c = compareObject_count( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500,
                'extra' : 'property'
        } );
        return a + '/' + b + '/' + c;
}
 
/**
 * The FOR IN-Method
 */
compareObject_forIn = function ( objectA, objectB ) {
 
        // Do a simple check if the types match
        if ( typeof( objectA ) == typeof( objectB ) ) {
 
                // Only loop over the contents if it really is an object
                if ( typeof( objectA ) == 'object' ) {
                        // If they are aliases of the same object (ie. mw and mediaWiki) return now
                        if ( objectA === objectB ) {
                                return true;
                        } else {
                                // Iterate over each property
                                for ( var prop in objectA ) {
                                        // Check if this property is also present in the other object
                                        if ( prop in objectB ) {
                                                // Compare the types of the properties
                                                var type = typeof( objectA[prop] );
                                                if ( type == typeof( objectB[prop] ) ) {
                                                        // Recursively check objects inside this one
                                                        switch ( type ) {
                                                                case 'object' :
                                                                        if ( !compareObject_forIn( objectA[prop], objectB[prop] ) ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                                case 'function' :
                                                                        if ( objectA[prop].toString() !== objectB[prop].toString() ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                                default:
                                                                        if ( objectA[prop] !== objectB[prop] ) {
                                                                                return false;
                                                                        }
                                                                        break;
                                                        }
                                                } else {
                                                        return false;
                                                }
                                        } else {
                                                return false;
                                        }
                                }
                                // Check for properties in B but not in A
                                // This is about 15% faster (tested in Safari 5 and Firefox 3.6)
                                // ...than incrementing a count variable in the above and below loops
                                // and comparing the numbers afterwards
                                //
                                for ( var prop in objectB ) {
                                        if ( !( prop in objectA ) ) {
                                                return false;
                                        }
                                }
                        }
                }
        } else {
                return false;
        }
        return true;
}
 
function runTest_forIn(){
        // the same
        var a = compareObject_forIn( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        } );
        // different
        var b = compareObject_forIn( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 510
        } );
        // extra stuff in B
        var c = compareObject_forIn( {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500
        }, {
                'foo' : 'bar',
                'string' : 'here',
                'func' : function(){ alert('test'); },
                'obj' : {
                        'foo' : 'string'
                },
                'num' : 500,
                'extra' : 'property'
        } );
        return a + '/' + b + '/' + c;
}
 
/**
 * The test execution
 */
var numerOfTests = 1000;
function runAllTests(){
        for ( i=0; i < numerOfTests; i++ ) {
                runTest_count();
                runTest_forIn();
        }
}

[edit] Results

The following results were achieved like this:

  • Open Safari 5 on a Mac
  • Go to about:blank
  • Open up the Web Inspector to the Console
  • Paste the above script
  • Go to Profiling and start recording
  • Execute runAllTests() twice
  • Stop recording
  • Results:
Self Total Cals Function
0.39% 1.27% 2000 runTest_count
0.17% 1.07% 2000 runTest_forIn
(1-(1.07/1.27))*100 = 15.7%
Personal tools
Namespaces

Variants
Actions
Navigation
Support
Download
Development
Communication
Print/export
Toolbox