Created
January 24, 2012 01:36
-
-
Save polotek/1667184 to your computer and use it in GitHub Desktop.
Deep object clone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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