Skip to content

Instantly share code, notes, and snippets.

@polotek
Created January 24, 2012 01:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save polotek/1667184 to your computer and use it in GitHub Desktop.
Save polotek/1667184 to your computer and use it in GitHub Desktop.
Deep object clone
// From benchmark.js - https://github.com/bestiejs/benchmark.js/blob/33aca33e4986386d782bbc7c2ab85cf34ed90bd2/benchmark.js
/**
* A deep clone utility.
* @static
* @memberOf Benchmark
* @param {Mixed} value The value to clone.
* @returns {Mixed} The cloned value.
*/
function deepClone(value) {
var accessor,
circular,
clone,
ctor,
descriptor,
extensible,
key,
length,
markerKey,
parent,
result,
source,
subIndex,
data = { 'value': value },
index = 0,
marked = [],
unmarked = [],
queue = [];
/**
* An easily detectable decorator for cloned values.
*/
function Marker(object) {
this.raw = object;
}
/**
* Gets an available marker key for the given object.
*/
function getMarkerKey(object) {
// avoid collisions with existing keys
var result = uid;
while (object[result] && object[result].constructor != Marker) {
result += 1;
}
return result;
}
/**
* The callback used by `forProps()`.
*/
function propCallback(subValue, subKey) {
// exit early to avoid cloning the marker
if (subValue && subValue.constructor == Marker) {
return;
}
// add objects to the queue
if (subValue === Object(subValue)) {
queue[queue.length] = { 'key': subKey, 'parent': clone, 'source': value };
}
// assign non-objects
else {
clone[subKey] = subValue;
}
}
do {
key = data.key;
parent = data.parent;
source = data.source;
clone = value = source ? source[key] : data.value;
accessor = circular = descriptor = false;
// create a basic clone to filter out functions, DOM elements, and
// other non `Object` objects
if (value === Object(value)) {
ctor = value.constructor;
switch (toString.call(value)) {
case '[object Array]':
clone = new ctor(value.length);
break;
case '[object Boolean]':
clone = new ctor(value == true);
break;
case '[object Date]':
clone = new ctor(+value);
break;
case '[object Object]':
isObject(value) && (clone = new ctor);
break;
case '[object Number]':
case '[object String]':
clone = new ctor(value);
break;
case '[object RegExp]':
clone = ctor(value.source,
(value.global ? 'g' : '') +
(value.ignoreCase ? 'i' : '') +
(value.multiline ? 'm' : ''));
}
// continue clone if `value` doesn't have an accessor descriptor
// http://es5.github.com/#x8.10.1
if (clone != value &&
!(descriptor = source && has.descriptors && getDescriptor(source, key),
accessor = descriptor && (descriptor.get || descriptor.set))) {
// use an existing clone (circular reference)
if ((extensible = isExtensible(value))) {
markerKey = getMarkerKey(value);
if (value[markerKey]) {
circular = clone = value[markerKey].raw;
}
} else {
// for frozen/sealed objects
for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) {
data = unmarked[subIndex];
if (data.object === value) {
circular = clone = data.clone;
break;
}
}
}
if (!circular) {
// mark object to allow quickly detecting circular references and tie it to its clone
if (extensible) {
value[markerKey] = new Marker(clone);
marked.push({ 'key': markerKey, 'object': value });
} else {
// for frozen/sealed objects
unmarked.push({ 'clone': clone, 'object': value });
}
// iterate over object properties
forProps(value, propCallback, { 'which': 'all' });
}
}
}
if (parent) {
// for custom property descriptors
if (accessor || (descriptor &&
!(descriptor.configurable && descriptor.enumerable && descriptor.writable))) {
descriptor.value && (descriptor.value = clone);
setDescriptor(parent, key, descriptor);
}
// for default property descriptors
else {
parent[key] = clone;
}
} else {
result = clone;
}
} while ((data = queue[index++]));
// remove markers
for (index = 0, length = marked.length; index < length; index++) {
data = marked[index];
delete data.object[data.key];
}
return result;
}
function clone(object, deep) {
var length, result, constructor, i = -1;
if (object) {
if (Object.isFunction(object.clone)) {
return object.clone(deep);
}
if (typeof object == 'object') {
constructor = object.constructor;
switch (Object.prototype.toString.call(object)) {
case '[object Array]' :
if (deep) {
result = constructor();
length = object.length;
while (++i < length) {
result[i] = Object.clone(object[i], deep);
}
} else {
result = object.slice(0);
}
return result;
case '[object RegExp]' :
return constructor(object.source,
(object.global ? 'g' : '') +
(object.ignoreCase ? 'i' : '') +
(object.multiline ? 'm' : ''));
case '[object Number]' :
case '[object String]' : return new constructor(object);
case '[object Boolean]' : return new constructor(object == true);
case '[object Date]' : return new constructor(+object);
}
result = Object();
if (deep) {
Object.each(object, function(value, key) {
result[key] = Object.clone(value, deep);
});
} else {
Object.extend(result, object);
}
return result;
}
}
return Object();
}
function extend(destination, source) {
Object.each(source, function(value, key) { destination[key] = value; });
return destination;
}
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// 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 plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// 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;
}
}
}
}
// Return the modified object
return target;
};
// port of jQuery.extend to node.js
/*!
* node.extend
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* @fileoverview
* Port of jQuery.extend that actually works on node.js
*/
function isPlainObject( obj ){
var has_own_constructor, has_is_property_of_method, key;
if( !obj || {}.toString.call( obj ) !== '[object Object]' || obj.nodeType || obj.setInterval ){
return false;
}
has_own_constructor = hasOwnProperty.call( obj, 'constructor' );
has_is_property_of_method = hasOwnProperty.call( obj.constructor.prototype, 'isPrototypeOf' );
// Not own constructor property must be Object
if( obj.constructor && !has_own_constructor && !has_is_property_of_method ){
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
for( key in obj ){}
return key === undefined || hasOwnProperty.call( obj, key );
};
function extend () {
var options, name, src, copy, copyIsArray, clone;
var target = arguments[ 0 ] || {};
var i = 1;
var length = arguments.length;
var deep = false;
// 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' && typeof target !== 'function' ){
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 plain objects or arrays
if( deep && copy && ( isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy )))){
if( copyIsArray ){
copyIsArray = false;
clone = src && Array.isArray( src ) ? src : [];
} else {
clone = src && isPlainObject( src ) ? src : {};
}
// Never move original objects, clone them
target[ name ] = extend( deep, clone, copy );
// Don't bring in undefined values
}else if( copy !== undefined ){
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
extend.version = '0.0.2';
module.exports = extend;
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment