Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use of prototype._clone() if found to clone an object. + test #93

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 48 additions & 38 deletions clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"rictic (https://github.com/rictic)",
"Martin Jurča (https://github.com/jurca)",
"Misery Lee <miserylee@foxmail.com> (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": {
Expand Down
51 changes: 51 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};