Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit ec8d488

Browse files
committed
perf(copy): only validate/clear user specified destination
1 parent a69251a commit ec8d488

File tree

2 files changed

+100
-81
lines changed

2 files changed

+100
-81
lines changed

src/Angular.js

+87-81
Original file line numberDiff line numberDiff line change
@@ -783,100 +783,106 @@ function arrayRemove(array, value) {
783783
</file>
784784
</example>
785785
*/
786-
function copy(source, destination, stackSource, stackDest) {
787-
if (isWindow(source) || isScope(source)) {
788-
throw ngMinErr('cpws',
789-
"Can't copy! Making copies of Window or Scope instances is not supported.");
790-
}
791-
if (isTypedArray(destination)) {
792-
throw ngMinErr('cpta',
793-
"Can't copy! TypedArray destination cannot be mutated.");
794-
}
795-
796-
if (!destination) {
797-
destination = source;
798-
if (isObject(source)) {
799-
var index;
800-
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
801-
return stackDest[index];
802-
}
803-
804-
// TypedArray, Date and RegExp have specific copy functionality and must be
805-
// pushed onto the stack before returning.
806-
// Array and other objects create the base object and recurse to copy child
807-
// objects. The array/object will be pushed onto the stack when recursed.
808-
if (isArray(source)) {
809-
return copy(source, [], stackSource, stackDest);
810-
} else if (isTypedArray(source)) {
811-
destination = new source.constructor(source);
812-
} else if (isDate(source)) {
813-
destination = new Date(source.getTime());
814-
} else if (isRegExp(source)) {
815-
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
816-
destination.lastIndex = source.lastIndex;
817-
} else {
818-
var emptyObject = Object.create(getPrototypeOf(source));
819-
return copy(source, emptyObject, stackSource, stackDest);
820-
}
821-
822-
if (stackDest) {
823-
stackSource.push(source);
824-
stackDest.push(destination);
825-
}
786+
function copy(source, destination) {
787+
if (destination) {
788+
if (isTypedArray(destination)) {
789+
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
826790
}
827-
} else {
828-
if (source === destination) throw ngMinErr('cpi',
829-
"Can't copy! Source and destination are identical.");
830-
831-
stackSource = stackSource || [];
832-
stackDest = stackDest || [];
833-
834-
if (isObject(source)) {
835-
stackSource.push(source);
836-
stackDest.push(destination);
791+
if (source === destination) {
792+
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
837793
}
838794

839-
var result, key;
840-
if (isArray(source)) {
795+
// Empty the destination object
796+
if (isArray(destination)) {
841797
destination.length = 0;
842-
for (var i = 0; i < source.length; i++) {
843-
destination.push(copy(source[i], null, stackSource, stackDest));
844-
}
845798
} else {
846-
var h = destination.$$hashKey;
847-
if (isArray(destination)) {
848-
destination.length = 0;
849-
} else {
850-
forEach(destination, function(value, key) {
799+
forEach(destination, function(value, key) {
800+
if (key !== '$$hashKey') {
851801
delete destination[key];
852-
});
853-
}
854-
if (isBlankObject(source)) {
855-
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
856-
for (key in source) {
857-
destination[key] = copy(source[key], null, stackSource, stackDest);
858-
}
859-
} else if (source && typeof source.hasOwnProperty === 'function') {
860-
// Slow path, which must rely on hasOwnProperty
861-
for (key in source) {
862-
if (source.hasOwnProperty(key)) {
863-
destination[key] = copy(source[key], null, stackSource, stackDest);
864-
}
865-
}
866-
} else {
867-
// Slowest path --- hasOwnProperty can't be called as a method
868-
for (key in source) {
869-
if (hasOwnProperty.call(source, key)) {
870-
destination[key] = copy(source[key], null, stackSource, stackDest);
871-
}
872802
}
803+
});
804+
}
805+
806+
return _copyRecurse(source, destination, [source], [destination]);
807+
}
808+
809+
return _copy(source, [], []);
810+
}
811+
812+
function _copyRecurse(source, destination, stackSource, stackDest) {
813+
var h = destination.$$hashKey;
814+
var result, key;
815+
if (isArray(source)) {
816+
for (var i = 0, ii = source.length; i < ii; i++) {
817+
destination.push(_copy(source[i], stackSource, stackDest));
818+
}
819+
} else if (isBlankObject(source)) {
820+
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
821+
for (key in source) {
822+
destination[key] = _copy(source[key], stackSource, stackDest);
823+
}
824+
} else if (source && typeof source.hasOwnProperty === 'function') {
825+
// Slow path, which must rely on hasOwnProperty
826+
for (key in source) {
827+
if (source.hasOwnProperty(key)) {
828+
destination[key] = _copy(source[key], stackSource, stackDest);
829+
}
830+
}
831+
} else {
832+
// Slowest path --- hasOwnProperty can't be called as a method
833+
for (key in source) {
834+
if (hasOwnProperty.call(source, key)) {
835+
destination[key] = _copy(source[key], stackSource, stackDest);
873836
}
874-
setHashKey(destination,h);
875837
}
876838
}
839+
setHashKey(destination, h);
877840
return destination;
878841
}
879842

843+
function _copy(source, stackSource, stackDest) {
844+
// Simple values
845+
if (!isObject(source)) {
846+
return source;
847+
}
848+
849+
// Already copied values
850+
var index = stackSource.indexOf(source);
851+
if (index !== -1) {
852+
return stackDest[index];
853+
}
854+
855+
if (isWindow(source) || isScope(source)) {
856+
throw ngMinErr('cpws',
857+
"Can't copy! Making copies of Window or Scope instances is not supported.");
858+
}
859+
860+
var needsRecurse = false;
861+
var destination;
862+
863+
if (isArray(source)) {
864+
destination = [];
865+
needsRecurse = true;
866+
} else if (isTypedArray(source)) {
867+
destination = new source.constructor(source);
868+
} else if (isDate(source)) {
869+
destination = new Date(source.getTime());
870+
} else if (isRegExp(source)) {
871+
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
872+
destination.lastIndex = source.lastIndex;
873+
} else {
874+
destination = Object.create(getPrototypeOf(source));
875+
needsRecurse = true;
876+
}
877+
878+
stackSource.push(source);
879+
stackDest.push(destination);
880+
881+
return needsRecurse
882+
? _copyRecurse(source, destination, stackSource, stackDest)
883+
: destination;
884+
}
885+
880886
/**
881887
* Creates a shallow copy of an object, an array or a primitive.
882888
*

test/AngularSpec.js

+13
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,19 @@ describe('angular', function() {
313313
it('should throw an exception if a Scope is being copied', inject(function($rootScope) {
314314
expect(function() { copy($rootScope.$new()); }).
315315
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
316+
expect(function() { copy({child: $rootScope.$new()}, {}); }).
317+
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
318+
expect(function() { copy([$rootScope.$new()]); }).
319+
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
316320
}));
317321

318322
it('should throw an exception if a Window is being copied', function() {
319323
expect(function() { copy(window); }).
320324
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
325+
expect(function() { copy({child: window}); }).
326+
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
327+
expect(function() { copy([window], []); }).
328+
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
321329
});
322330

323331
it('should throw an exception when source and destination are equivalent', function() {
@@ -334,6 +342,11 @@ describe('angular', function() {
334342
hashKey(src);
335343
dst = copy(src);
336344
expect(hashKey(dst)).not.toEqual(hashKey(src));
345+
346+
src = {foo: {}};
347+
hashKey(src.foo);
348+
dst = copy(src);
349+
expect(hashKey(src.foo)).not.toEqual(hashKey(dst.foo));
337350
});
338351

339352
it('should retain the previous $$hashKey when copying object with hashKey', function() {

0 commit comments

Comments
 (0)