Skip to content

Commit 8a2503b

Browse files
committed
perf(copy): reduce recursion, unnecessary validation, unnecessary clearing of destination after initializing
1 parent 18f5f31 commit 8a2503b

File tree

2 files changed

+63
-57
lines changed

2 files changed

+63
-57
lines changed

src/Angular.js

Lines changed: 62 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -709,77 +709,82 @@ function arrayRemove(array, value) {
709709
</example>
710710
*/
711711
function copy(source, destination, stackSource, stackDest) {
712-
if (isWindow(source) || isScope(source)) {
713-
throw ngMinErr('cpws',
714-
"Can't copy! Making copies of Window or Scope instances is not supported.");
715-
}
716-
if (isTypedArray(destination)) {
717-
throw ngMinErr('cpta',
718-
"Can't copy! TypedArray destination cannot be mutated.");
719-
}
720-
721-
if (!destination) {
722-
destination = source;
723-
if (isObject(source)) {
724-
var index;
725-
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
726-
return stackDest[index];
727-
}
728-
729-
if (isArray(source)) {
730-
return copy(source, [], stackSource, stackDest);
731-
} else if (isTypedArray(source)) {
732-
destination = new source.constructor(source);
733-
} else if (isDate(source)) {
734-
destination = new Date(source.getTime());
735-
} else if (isRegExp(source)) {
736-
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
737-
destination.lastIndex = source.lastIndex;
738-
} else {
739-
var emptyObject = Object.create(Object.getPrototypeOf(source));
740-
return copy(source, emptyObject, stackSource, stackDest);
741-
}
712+
// Validate and clear any passed destination
713+
var destinationHashKey;
714+
if (destination) {
715+
if (source === destination) {
716+
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
717+
}
718+
if (isTypedArray(destination)) {
719+
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
720+
}
742721

743-
if (stackDest) {
744-
stackSource.push(source);
745-
stackDest.push(destination);
722+
if (isArray(destination)) {
723+
destination.length = 0;
724+
} else if (isObject(destination)) {
725+
destinationHashKey = destination.$$hashKey;
726+
for (var dkey in destination) {
727+
if (destination.hasOwnProperty(dkey)) {
728+
delete destination[dkey];
729+
}
746730
}
747731
}
748-
} else {
749-
if (source === destination) throw ngMinErr('cpi',
750-
"Can't copy! Source and destination are identical.");
751-
752-
stackSource = stackSource || [];
753-
stackDest = stackDest || [];
732+
}
754733

755-
if (isObject(source)) {
756-
stackSource.push(source);
757-
stackDest.push(destination);
734+
// Copy and recurse objects
735+
if (isObject(source)) {
736+
var index;
737+
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
738+
return stackDest[index];
758739
}
759740

741+
var traverse = false;
760742
if (isArray(source)) {
761-
destination.length = 0;
762-
for (var i = 0; i < source.length; i++) {
763-
destination.push(copy(source[i], null, stackSource, stackDest));
764-
}
743+
destination = destination || new Array(source.length);
744+
traverse = true;
745+
} else if (!destination && isTypedArray(source)) {
746+
destination = new source.constructor(source);
747+
} else if (!destination && isDate(source)) {
748+
destination = new Date(source.getTime());
749+
} else if (!destination && isRegExp(source)) {
750+
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
751+
destination.lastIndex = source.lastIndex;
765752
} else {
766-
var h = destination.$$hashKey;
767-
if (isArray(destination)) {
768-
destination.length = 0;
769-
} else {
770-
forEach(destination, function(value, key) {
771-
delete destination[key];
772-
});
753+
if (isWindow(source) || isScope(source)) {
754+
throw ngMinErr('cpws',
755+
"Can't copy! Making copies of Window or Scope instances is not supported.");
773756
}
774-
for (var key in source) {
775-
if (source.hasOwnProperty(key)) {
776-
destination[key] = copy(source[key], null, stackSource, stackDest);
757+
758+
destination = destination || Object.create(Object.getPrototypeOf(source));
759+
traverse = true;
760+
}
761+
762+
(stackSource || (stackSource = [])).push(source);
763+
(stackDest || (stackDest = [])).push(destination);
764+
765+
if (traverse) {
766+
if (isArray(source)) {
767+
for (var i = 0, ii = destination.length = source.length; i < ii; i++) {
768+
destination[i] = copy(source[i], null, stackSource, stackDest);
769+
}
770+
} else {
771+
destinationHashKey = destinationHashKey || false;
772+
for (var key in source) {
773+
if (source.hasOwnProperty(key)) {
774+
destination[key] = copy(source[key], null, stackSource, stackDest);
775+
}
777776
}
778777
}
779-
setHashKey(destination,h);
780778
}
779+
// Otherwise assign simple properties
780+
} else if (!destination) {
781+
destination = source;
782+
}
781783

784+
if (destinationHashKey !== undefined) {
785+
setHashKey(destination, destinationHashKey);
782786
}
787+
783788
return destination;
784789
}
785790

test/AngularSpec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ describe('angular', function() {
428428
expect(copy(null, {0:1,1:2,2:3})).toEqual({});
429429
expect(copy(undefined, {0:1,1:2,2:3})).toEqual({});
430430
expect(copy(new Date(), {0:1,1:2,2:3})).toEqual({});
431+
expect(copy([4,5], {0:1,1:2,2:3})).toEqual({0:4,1:5,length:2});
431432
expect(copy(/a/, {0:1,1:2,2:3})).toEqual({});
432433
expect(copy(true, {0:1,1:2,2:3})).toEqual({});
433434
});

0 commit comments

Comments
 (0)