diff --git a/README.md b/README.md index c21ba16..e508dbc 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,21 @@ So, `b.myself` points to `b`, not `a`. Neat! ## Changelog +#### 2018-03-22 + + - Add detection of a custom _clone function on an object. +If present, use this function to clone the object and all children. +This allows for the cloning of objects in any way you wish, +and is especially useful if the object is not clonable in the normal way. +the _clone() fn can be on the object prototype, or directly on the object as a method, +and should return the desired cloned object. See tests for examples. + +### v2.1.2 + +#### 2018-03-21 + + - Use `Buffer.allocUnsafe()` on Node >= 4.5.0 (contributed by @ChALkeR) + ### v2.1.1 #### 2017-03-09 diff --git a/clone.js b/clone.js index 80d0c76..ee689ab 100644 --- a/clone.js +++ b/clone.js @@ -78,6 +78,8 @@ function clone(parent, circular, depth, prototype, includeNonEnumerable) { if (depth === 0) return parent; + var dochildren = true; + var child; var proto; if (typeof parent != 'object') { @@ -112,7 +114,13 @@ function clone(parent, circular, depth, prototype, includeNonEnumerable) { } else { if (typeof prototype == 'undefined') { proto = Object.getPrototypeOf(parent); - child = Object.create(proto); + // if a _clone function in prototype OR direct on object + if (parent._clone){ + child = parent._clone(); + dochildren = false; + } else { + child = Object.create(proto); + } } else { child = Object.create(prototype); @@ -144,52 +152,54 @@ function clone(parent, circular, depth, prototype, includeNonEnumerable) { }); } - for (var i in parent) { - var attrs; - if (proto) { - attrs = Object.getOwnPropertyDescriptor(proto, i); - } - - if (attrs && attrs.set == null) { - continue; - } - child[i] = _clone(parent[i], depth - 1); - } + // if we used _clone(), then ignore the rest of this object + if (dochildren){ + for (var i in parent) { + var attrs; + if (proto) { + attrs = Object.getOwnPropertyDescriptor(proto, i); + } - if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(parent); - for (var i = 0; i < symbols.length; i++) { - // Don't need to worry about cloning a symbol because it is a primitive, - // like a number or string. - var symbol = symbols[i]; - var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); - if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { + if (attrs && attrs.set == null) { continue; } - child[symbol] = _clone(parent[symbol], depth - 1); - if (!descriptor.enumerable) { - Object.defineProperty(child, symbol, { - enumerable: false - }); + child[i] = _clone(parent[i], depth - 1); + } + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(parent); + for (var i = 0; i < symbols.length; i++) { + // Don't need to worry about cloning a symbol because it is a primitive, + // like a number or string. + var symbol = symbols[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); + if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { + continue; + } + child[symbol] = _clone(parent[symbol], depth - 1); + if (!descriptor.enumerable) { + Object.defineProperty(child, symbol, { + enumerable: false + }); + } } } - } - if (includeNonEnumerable) { - var allPropertyNames = Object.getOwnPropertyNames(parent); - for (var i = 0; i < allPropertyNames.length; i++) { - var propertyName = allPropertyNames[i]; - var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); - if (descriptor && descriptor.enumerable) { - continue; + if (includeNonEnumerable) { + var allPropertyNames = Object.getOwnPropertyNames(parent); + for (var i = 0; i < allPropertyNames.length; i++) { + var propertyName = allPropertyNames[i]; + var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); + if (descriptor && descriptor.enumerable) { + continue; + } + child[propertyName] = _clone(parent[propertyName], depth - 1); + Object.defineProperty(child, propertyName, { + enumerable: false + }); } - child[propertyName] = _clone(parent[propertyName], depth - 1); - Object.defineProperty(child, propertyName, { - enumerable: false - }); } } - return child; } diff --git a/package.json b/package.json index ce8d7e3..2e1741b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "rictic (https://github.com/rictic)", "Martin JurĨa (https://github.com/jurca)", "Misery Lee (https://github.com/miserylee)", - "Clemens Wolff (https://github.com/c-w)" + "Clemens Wolff (https://github.com/c-w)", + "Simon Hailes (https://github.com/btsimonh)" + ], "license": "MIT", "engines": { diff --git a/test.js b/test.js index fb4ba67..b7d4628 100644 --- a/test.js +++ b/test.js @@ -684,3 +684,54 @@ exports["clone should mark the cloned non-enumerable properties as non-enumerabl test.done(); }; + + +exports["clone object using _clone"] = function (test) { + test.expect(5); + + + // create an object with _clone prototype + function myobj( name, id ){ + this.name = name; + this.id = id; + }; + + // note clone function does not copy complete object. + myobj.prototype._clone = function(){ + var newobj = new myobj( this.name, 'isclone'); + return newobj; + } + + + var source = new myobj('myname', 'original'); + + // also test a (different) direct on object _clone() method + source._clone = function(){ + var newobj = new myobj( this.name, 'isclone2'); + return newobj; + } + + // this will use the *function* we just added to the source object + var cloned = clone(source); + // this will use the prototype we added to the *object defn* + var cloned2 = clone(cloned); + + // ensure that out cloned object still has the prototype _clone() + var fail = false; + if (!cloned._clone){ + fail = true; + } + + var util = require('util'); + console.log(util.inspect(cloned)); + console.log(util.inspect(cloned2)); + + test.equal(cloned.name, source.name); + test.equal(cloned.id, 'isclone2'); + test.equal(cloned2.id, 'isclone'); + test.equal(source.id, 'original'); + test.equal(fail, false); + + test.done(); +}; +