Skip to content

Commit

Permalink
added support for cloning of non-enumerable own object properties
Browse files Browse the repository at this point in the history
  • Loading branch information
jurca committed Nov 14, 2016
1 parent 636ea69 commit 74a5cb1
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 2 deletions.
28 changes: 26 additions & 2 deletions clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ try {
* a particular depth. (optional - defaults to Infinity)
* @param `prototype` - sets the prototype to be used when cloning an object.
* (optional - defaults to parent prototype).
* @param `includeNonEnumerable` - set to true if the non-enumerable properties
* should be cloned as well. Non-enumerable properties on the prototype
* chain will be ignored. (optional - false by default)
*/
function clone(parent, circular, depth, prototype) {
function clone(parent, circular, depth, prototype, includeNonEnumerable) {
var filter;
if (typeof circular === 'object') {
depth = circular.depth;
prototype = circular.prototype;
filter = circular.filter;
includeNonEnumerable = circular.includeNonEnumerable;
circular = circular.circular;
}
// maintain two arrays for circular references, where corresponding parents
Expand Down Expand Up @@ -167,10 +171,30 @@ function clone(parent, circular, depth, prototype) {
// like a number or string.
var symbol = symbols[i];
var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
if (descriptor && !descriptor.enumerable) {
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;
}
child[propertyName] = _clone(parent[propertyName], depth - 1);
Object.defineProperty(child, propertyName, {
enumerable: false
});
}
}

Expand Down
130 changes: 130 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,3 +554,133 @@ if (nativeSymbol) {
test.done();
};
}

exports["clone should ignore non-enumerable properties by default"] = function (test) {
test.expect(5);

var nativeSymbol;
try {
nativeSymbol = Symbol
} catch(_) {
nativeSymbol = function(id) {
return '__symbol__:' + id
}
}

var source = {
x: 1,
y: 2
};
Object.defineProperty(source, 'y', {
enumerable: false
});
Object.defineProperty(source, 'z', {
value: 3
});
var symbol1 = nativeSymbol('a');
var symbol2 = nativeSymbol('b');
source[symbol1] = 4;
source[symbol2] = 5;
Object.defineProperty(source, symbol2, {
enumerable: false
});

var cloned = clone(source);
test.equal(cloned.x, 1);
test.equal(Object.hasOwnProperty(cloned, 'y'), false);
test.equal(Object.hasOwnProperty(cloned, 'z'), false);
test.equal(cloned[symbol1], 4);
test.equal(Object.hasOwnProperty(cloned, symbol2), false);

test.done();
};

exports["clone should support cloning non-enumerable properties"] = function (test) {
test.expect(6);

var nativeSymbol;
try {
nativeSymbol = Symbol
} catch(_) {
nativeSymbol = function(id) {
return '__symbol__:' + id
}
}

var source = { x: 1, b: [2] };
Object.defineProperty(source, 'b', {
enumerable: false
});
var symbol = nativeSymbol('a');
source[symbol] = { x: 3 };
Object.defineProperty(source, symbol, {
enumerable: false
});

var cloned = clone(source, false, Infinity, undefined, true);
test.equal(cloned.x, 1);
test.equal(cloned.b instanceof Array, true);
test.equal(cloned.b.length, 1);
test.equal(cloned.b[0], 2);
test.equal(cloned[symbol] instanceof Object, true);
test.equal(cloned[symbol].x, 3);

test.done();
};

exports["clone should allow enabling the cloning of non-enumerable properties via an options object"] = function (test) {
test.expect(1);

var source = { x: 1 };
Object.defineProperty(source, 'x', {
enumerable: false
});

var cloned = clone(source, {
includeNonEnumerable: true
});
test.equal(cloned.x, 1);

test.done();
};

exports["clone should mark the cloned non-enumerable properties as non-enumerable"] = function (test) {
test.expect(4);

var nativeSymbol;
try {
nativeSymbol = Symbol
} catch(_) {
nativeSymbol = function(id) {
return '__symbol__:' + id
}
}

var source = { x: 1, y: 2 };
Object.defineProperty(source, 'y', {
enumerable: false
});
var symbol1 = nativeSymbol('a');
var symbol2 = nativeSymbol('b');
source[symbol1] = 3;
source[symbol2] = 4;
Object.defineProperty(source, symbol2, {
enumerable: false
});

var cloned = clone(source, {
includeNonEnumerable: true
});
test.equal(Object.getOwnPropertyDescriptor(cloned, 'x').enumerable, true);
test.equal(Object.getOwnPropertyDescriptor(cloned, 'y').enumerable, false);
test.equal(
Object.getOwnPropertyDescriptor(cloned, symbol1).enumerable,
true
);
test.equal(
Object.getOwnPropertyDescriptor(cloned, symbol2).enumerable,
false
);

test.done();
};

0 comments on commit 74a5cb1

Please sign in to comment.