Last active
May 30, 2020 15:42
-
-
Save jherax/05204bdf9eb47eeffdc8 to your computer and use it in GitHub Desktop.
Clones or extends an object (deep copy) supporting objects with circular references
This file contains 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
/** | |
* @author | |
* David Rivera (jherax) | |
* https://github.com/jherax | |
*/ | |
/* eslint-disable no-bitwise */ | |
/** @private */ | |
const toString = Object.prototype.toString; | |
const constructors = [Date, RegExp, Function, String, Number, Boolean]; | |
const isNotObject = value => value === null || typeof value !== 'object'; | |
// Add support for some ES2015 constructors | |
['Map', 'Set'].forEach((c) => { | |
if (c in window) constructors.push(window[c]); | |
}); | |
/** | |
* @private | |
* Compares an object against the context. | |
* | |
* @param {any} obj: the object to compare | |
* @returns {Boolean} | |
*/ | |
function compare(obj) { | |
return obj === this; | |
} | |
/** | |
* @private | |
* Creates a deep copy of an object supporting circular structures. | |
* | |
* @param {any} source: the object to clone | |
* @param {Object} dest: (optional) object to extend | |
* @param {Array} cache: the cache for cloned objects | |
* @return {any} the object cloned | |
*/ | |
function cloner(source, dest, cache) { | |
let prop; | |
// determines whether @source is a primitive value or a function | |
if (isNotObject(source)) return source; | |
// checks if @source refers to an object created previously | |
if (toString.call(source) === '[object Object]') { | |
if (cache.some(compare, source)) return source; | |
// keeps references of created objects | |
// to prevent a circular structure exception | |
cache.push(source); | |
} | |
// determines if the @source is a DOM Node | |
if (source.nodeName) return source.cloneNode(true); | |
// determines whether @source is an instance of any of the constructors | |
if (~constructors.indexOf(source.constructor)) return new source.constructor(source); | |
if (source.constructor !== Object && source.constructor !== Array) return source; | |
// creates a new object and recursively iterates over its properties | |
dest = dest || new source.constructor(); | |
for (prop in source) { | |
// merges @source into @dest | |
dest[prop] = cloner(source[prop], dest[prop], cache); | |
} | |
return dest; | |
} | |
/** | |
* @public | |
* Creates a deep copy of an object supporting objects with circular references. | |
* | |
* @param {any} source: the object to clone | |
* @return {any} | |
*/ | |
function clone(source) { | |
// the cache prevents circular reference exception! | |
return cloner(source, null, []); | |
} | |
/** | |
* @public | |
* Merges an object into another supporting objects with circular references. | |
* Recursively descends into object properties of source objects, performing a deep copy. | |
* | |
* @param {Object} dest: destination object to merge | |
* @param {Object} source: the object to copy from | |
* @return {Object} | |
*/ | |
function merge(dest, source) { | |
if (dest === source) return dest; | |
if (isNotObject(dest)) throw Error('First argument must be an object'); | |
cloner(source, dest, []); | |
return dest; | |
} |
This file contains 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
var freeman, david, cloned; | |
function Freeman() { | |
"use strict"; | |
this.name = "Gordon Freeman"; | |
this.character = "Freeman"; | |
this.friends = ["Barney Calhoun"]; | |
} | |
david = { | |
name: "David", | |
character: "jherax", | |
friends: ["John Carmack"], | |
languages: /javascript|c#|java|vb|sql/i, | |
greeting: function() { return "Hi, I am " + this.name }, | |
node: document.getElementsByClassName('js-code-editor')[1], | |
info: { birth: new Date() } | |
}; | |
// let's create a circular reference | |
freeman = new Freeman(); | |
freeman.friends.push(david); | |
david.friends.push(freeman); | |
// let's clone an object with circular reference | |
// No circular reference exception!! | |
var cloned = clone(david); | |
// let's modify some properties | |
cloned.name = "David Rivera"; | |
cloned.friends.push("Jim Rynor"); | |
// the original object was not affected! | |
console.log("original:", david.name, david.friends); | |
console.log("cloned:", cloned.name, cloned.friends); |
This file contains 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
/** | |
* Merging objects... | |
*/ | |
var config = { | |
container: 'body', | |
position: { | |
left: 0, | |
top: 0, | |
}, | |
}; | |
var options = { | |
element: '.tooltip', | |
position: {left: 'center'}, | |
}; | |
// merge the "options" object into "config" | |
// note the "position" property is not overridden but merged | |
// (Object.assign overrides existing properties) | |
merge(config, options); | |
console.log(config); | |
/** | |
* Merging arrays... | |
*/ | |
var dest = [1, 2, {a: 'a'}]; | |
merge(dest, [3, null, {b: 'b'}]); | |
console.log(dest); | |
// [3, null, {a: "a", b: "b"}] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
From the article: Clonando objetos en JavaScript
Follow the blog: jherax.wordpress.com