The extendCopy()
function discussed previously creates what is called a shallow copy of an object, just like extend2()
before that. The opposite of a shallow copy would be, naturally, a deep copy. As discussed previously (in the Heads-up when copying by reference section of this chapter), when you copy objects, you only copy pointers to the location in memory where the object is stored. This is what happens in a shallow copy. If you modify an object in the copy, you also modify the original. The deep copy avoids this problem.
The deep copy is implemented in the same way as the shallow copy-you loop through the properties and copy them one by one. However, when you encounter a property that points to an object, you call the deepcopy
function again:
function deepCopy(p, c) { c = c || {}; for (var i in p) { if (p.hasOwnProperty(i)) { if (typeof p[i] === 'object') { c[i] = Array.isArray(p[i]) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } } return c; }
Let's create an object that has arrays and a subobject as properties:
var parent = { numbers: [1, 2, 3], letters: ['a', 'b', 'c'], obj: { prop: 1 }, bool: true };
Let's test this by creating a deep copy and a shallow copy. Unlike the shallow copy, when you update the numbers
property of a deep copy, the original is not affected:
>var mydeep = deepCopy(parent); >var myshallow = extendCopy(parent); >mydeep.numbers.push(4,5,6); 6 >mydeep.numbers; [1, 2, 3, 4, 5, 6] >parent.numbers; [1, 2, 3] >myshallow.numbers.push(10); 4 >myshallow.numbers; [1, 2, 3, 10] >parent.numbers; [1, 2, 3, 10] >mydeep.numbers; [1, 2, 3, 4, 5, 6]
Two side notes about the deepCopy()
function:
hasOwnProperty()
is always a good idea to make sure you don't carry over someone's additions to the core prototypes.Array.isArray()
exists since ES5 because it's surprisingly hard otherwise to tell real arrays from objects. The best cross-browser solution (if you need to define isArray()
in ES3 browsers) looks a little hacky, but it works:if (Array.isArray !== "function") { Array.isArray = function (candidate) { return Object.prototype.toString.call(candidate) === '[object Array]'; }; }