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

Commit d0a665d

Browse files
committed
perf(copy): only validate/clear user specified destination
1 parent 7dcfe5e commit d0a665d

File tree

2 files changed

+102
-83
lines changed

2 files changed

+102
-83
lines changed

src/Angular.js

+89-83
Original file line numberDiff line numberDiff line change
@@ -794,102 +794,108 @@ function arrayRemove(array, value) {
794794
</file>
795795
</example>
796796
*/
797-
function copy(source, destination, stackSource, stackDest) {
798-
if (isWindow(source) || isScope(source)) {
799-
throw ngMinErr('cpws',
800-
"Can't copy! Making copies of Window or Scope instances is not supported.");
801-
}
802-
if (isTypedArray(destination)) {
803-
throw ngMinErr('cpta',
804-
"Can't copy! TypedArray destination cannot be mutated.");
805-
}
806-
807-
if (!destination) {
808-
destination = source;
809-
if (isObject(source)) {
810-
var index;
811-
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
812-
return stackDest[index];
813-
}
814-
815-
// TypedArray, Date and RegExp have specific copy functionality and must be
816-
// pushed onto the stack before returning.
817-
// Array and other objects create the base object and recurse to copy child
818-
// objects. The array/object will be pushed onto the stack when recursed.
819-
if (isArray(source)) {
820-
return copy(source, [], stackSource, stackDest);
821-
} else if (isTypedArray(source)) {
822-
destination = new source.constructor(source);
823-
} else if (isDate(source)) {
824-
destination = new Date(source.getTime());
825-
} else if (isRegExp(source)) {
826-
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
827-
destination.lastIndex = source.lastIndex;
828-
} else if (isFunction(source.cloneNode)) {
829-
destination = source.cloneNode(true);
830-
} else {
831-
var emptyObject = Object.create(getPrototypeOf(source));
832-
return copy(source, emptyObject, stackSource, stackDest);
833-
}
834-
835-
if (stackDest) {
836-
stackSource.push(source);
837-
stackDest.push(destination);
838-
}
797+
function copy(source, destination) {
798+
if (destination) {
799+
if (isTypedArray(destination)) {
800+
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
839801
}
840-
} else {
841-
if (source === destination) throw ngMinErr('cpi',
842-
"Can't copy! Source and destination are identical.");
843-
844-
stackSource = stackSource || [];
845-
stackDest = stackDest || [];
846-
847-
if (isObject(source)) {
848-
stackSource.push(source);
849-
stackDest.push(destination);
802+
if (source === destination) {
803+
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
850804
}
851805

852-
var result, key;
853-
if (isArray(source)) {
806+
// Empty the destination object
807+
if (isArray(destination)) {
854808
destination.length = 0;
855-
for (var i = 0; i < source.length; i++) {
856-
destination.push(copy(source[i], null, stackSource, stackDest));
857-
}
858809
} else {
859-
var h = destination.$$hashKey;
860-
if (isArray(destination)) {
861-
destination.length = 0;
862-
} else {
863-
forEach(destination, function(value, key) {
810+
forEach(destination, function(value, key) {
811+
if (key !== '$$hashKey') {
864812
delete destination[key];
865-
});
866-
}
867-
if (isBlankObject(source)) {
868-
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
869-
for (key in source) {
870-
destination[key] = copy(source[key], null, stackSource, stackDest);
871-
}
872-
} else if (source && typeof source.hasOwnProperty === 'function') {
873-
// Slow path, which must rely on hasOwnProperty
874-
for (key in source) {
875-
if (source.hasOwnProperty(key)) {
876-
destination[key] = copy(source[key], null, stackSource, stackDest);
877-
}
878-
}
879-
} else {
880-
// Slowest path --- hasOwnProperty can't be called as a method
881-
for (key in source) {
882-
if (hasOwnProperty.call(source, key)) {
883-
destination[key] = copy(source[key], null, stackSource, stackDest);
884-
}
885813
}
814+
});
815+
}
816+
817+
return _copyRecurse(source, destination, [source], [destination]);
818+
}
819+
820+
return _copy(source, [], []);
821+
}
822+
823+
function _copyRecurse(source, destination, stackSource, stackDest) {
824+
var h = destination.$$hashKey;
825+
var result, key;
826+
if (isArray(source)) {
827+
for (var i = 0, ii = source.length; i < ii; i++) {
828+
destination.push(_copy(source[i], stackSource, stackDest));
829+
}
830+
} else if (isBlankObject(source)) {
831+
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
832+
for (key in source) {
833+
destination[key] = _copy(source[key], stackSource, stackDest);
834+
}
835+
} else if (source && typeof source.hasOwnProperty === 'function') {
836+
// Slow path, which must rely on hasOwnProperty
837+
for (key in source) {
838+
if (source.hasOwnProperty(key)) {
839+
destination[key] = _copy(source[key], stackSource, stackDest);
840+
}
841+
}
842+
} else {
843+
// Slowest path --- hasOwnProperty can't be called as a method
844+
for (key in source) {
845+
if (hasOwnProperty.call(source, key)) {
846+
destination[key] = _copy(source[key], stackSource, stackDest);
886847
}
887-
setHashKey(destination,h);
888848
}
889849
}
850+
setHashKey(destination, h);
890851
return destination;
891852
}
892853

854+
function _copy(source, stackSource, stackDest) {
855+
// Simple values
856+
if (!isObject(source)) {
857+
return source;
858+
}
859+
860+
// Already copied values
861+
var index = stackSource.indexOf(source);
862+
if (index !== -1) {
863+
return stackDest[index];
864+
}
865+
866+
if (isWindow(source) || isScope(source)) {
867+
throw ngMinErr('cpws',
868+
"Can't copy! Making copies of Window or Scope instances is not supported.");
869+
}
870+
871+
var needsRecurse = false;
872+
var destination;
873+
874+
if (isArray(source)) {
875+
destination = [];
876+
needsRecurse = true;
877+
} else if (isTypedArray(source)) {
878+
destination = new source.constructor(source);
879+
} else if (isDate(source)) {
880+
destination = new Date(source.getTime());
881+
} else if (isRegExp(source)) {
882+
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
883+
destination.lastIndex = source.lastIndex;
884+
} else if (isFunction(source.cloneNode)) {
885+
destination = source.cloneNode(true);
886+
} else {
887+
destination = Object.create(getPrototypeOf(source));
888+
needsRecurse = true;
889+
}
890+
891+
stackSource.push(source);
892+
stackDest.push(destination);
893+
894+
return needsRecurse
895+
? _copyRecurse(source, destination, stackSource, stackDest)
896+
: destination;
897+
}
898+
893899
/**
894900
* Creates a shallow copy of an object, an array or a primitive.
895901
*

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)