Side navigation
Ticket #6958: jqExtendArrayBug.html
File jqExtendArrayBug.html, 4.2 KB (added by enideo, August 27, 2010 10:14AM UTC)
Example of the bug and fixes
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' lang='en' dir='ltr'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>jquery extend array bug</title>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js'></script>
<script type='text/javascript'>
$(document).ready(function(){
var target, original;
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
note('arrays properties for both objects : target (first) and newObject (second)');
note(target.array.join());
note(newObject.array.join());
$.extend(target, newObject);
note('target is extended by newObject (not deep)');
note(target.array.join());
note(newObject.array.join());
target.array[0] = 6;
note('first value of target array changed to 6');
note(target.array.join());
note(newObject.array.join());
note("BUG: newObject's array's first value also changes as it is referenced");
note("deep copy is not the solution, as the newObject array should replace the target array, not extend it:");
note('(RESETTING VARIABLES: DEEP COPY)');
target = {
array : [1, 2, 3]
};
original = {
array : [4, 5]
};
$.extend(true, target, original);
note("undesired behaviour: target array is too long");
note(target.array.join());
note(original.array.join());
target.array[0] = 6;
note("but the array has been correctly cloned, not referenced");
note(target.array.join());
note(original.array.join());
note("...implementing the proposed fix...");
jQuery.extend = jQuery.fn.extend = function() {
// copy reference to target object
var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging object literal values or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
: jQuery.isArray(copy) ? [] : {};
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
//target[ name ] = copy;
target[ name ] = ( jQuery.isArray!==undefined && jQuery.isArray(copy) )
? target[ name ] = copy.slice(0) : target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
$.extend(target, newObject);
target.array[0] = 6;
note('(RESETTING VARIABLES: PROPOSED FIX)');
note('final result (as desired)');
note(target.array.join());
note(newObject.array.join());
});
function note(msg){
$('#log').append( $('<p/>').text(msg) );
}
</script>
</head>
<body>
<p>original values</p>
<pre>
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
</pre>
<div id='log'>
</div>
</body>
</html>
Download in other formats:
Original Format
File jqExtendArrayBug.html, 4.2 KB (added by enideo, August 27, 2010 10:14AM UTC)
Example of the bug and fixes
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' lang='en' dir='ltr'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>jquery extend array bug</title>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js'></script>
<script type='text/javascript'>
$(document).ready(function(){
var target, original;
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
note('arrays properties for both objects : target (first) and newObject (second)');
note(target.array.join());
note(newObject.array.join());
$.extend(target, newObject);
note('target is extended by newObject (not deep)');
note(target.array.join());
note(newObject.array.join());
target.array[0] = 6;
note('first value of target array changed to 6');
note(target.array.join());
note(newObject.array.join());
note("BUG: newObject's array's first value also changes as it is referenced");
note("deep copy is not the solution, as the newObject array should replace the target array, not extend it:");
note('(RESETTING VARIABLES: DEEP COPY)');
target = {
array : [1, 2, 3]
};
original = {
array : [4, 5]
};
$.extend(true, target, original);
note("undesired behaviour: target array is too long");
note(target.array.join());
note(original.array.join());
target.array[0] = 6;
note("but the array has been correctly cloned, not referenced");
note(target.array.join());
note(original.array.join());
note("...implementing the proposed fix...");
jQuery.extend = jQuery.fn.extend = function() {
// copy reference to target object
var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging object literal values or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
: jQuery.isArray(copy) ? [] : {};
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
//target[ name ] = copy;
target[ name ] = ( jQuery.isArray!==undefined && jQuery.isArray(copy) )
? target[ name ] = copy.slice(0) : target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
$.extend(target, newObject);
target.array[0] = 6;
note('(RESETTING VARIABLES: PROPOSED FIX)');
note('final result (as desired)');
note(target.array.join());
note(newObject.array.join());
});
function note(msg){
$('#log').append( $('<p/>').text(msg) );
}
</script>
</head>
<body>
<p>original values</p>
<pre>
target = {
array : [1, 2, 3]
};
newObject = {
array : [4, 5]
};
</pre>
<div id='log'>
</div>
</body>
</html>