Skip to content

Commit

Permalink
perf(copy): reduce recursion, unnecessary validation, unnecessary cle…
Browse files Browse the repository at this point in the history
…aring of destination after initializing
  • Loading branch information
jbedard committed Mar 10, 2015
1 parent 18f5f31 commit 8a2503b
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 57 deletions.
119 changes: 62 additions & 57 deletions src/Angular.js
Expand Up @@ -709,77 +709,82 @@ function arrayRemove(array, value) {
</example>
*/
function copy(source, destination, stackSource, stackDest) {
if (isWindow(source) || isScope(source)) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
if (isTypedArray(destination)) {
throw ngMinErr('cpta',
"Can't copy! TypedArray destination cannot be mutated.");
}

if (!destination) {
destination = source;
if (isObject(source)) {
var index;
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
return stackDest[index];
}

if (isArray(source)) {
return copy(source, [], stackSource, stackDest);
} else if (isTypedArray(source)) {
destination = new source.constructor(source);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
} else {
var emptyObject = Object.create(Object.getPrototypeOf(source));
return copy(source, emptyObject, stackSource, stackDest);
}
// Validate and clear any passed destination
var destinationHashKey;
if (destination) {
if (source === destination) {
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
}
if (isTypedArray(destination)) {
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
}

if (stackDest) {
stackSource.push(source);
stackDest.push(destination);
if (isArray(destination)) {
destination.length = 0;
} else if (isObject(destination)) {
destinationHashKey = destination.$$hashKey;
for (var dkey in destination) {
if (destination.hasOwnProperty(dkey)) {
delete destination[dkey];
}
}
}
} else {
if (source === destination) throw ngMinErr('cpi',
"Can't copy! Source and destination are identical.");

stackSource = stackSource || [];
stackDest = stackDest || [];
}

if (isObject(source)) {
stackSource.push(source);
stackDest.push(destination);
// Copy and recurse objects
if (isObject(source)) {
var index;
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
return stackDest[index];
}

var traverse = false;
if (isArray(source)) {
destination.length = 0;
for (var i = 0; i < source.length; i++) {
destination.push(copy(source[i], null, stackSource, stackDest));
}
destination = destination || new Array(source.length);
traverse = true;
} else if (!destination && isTypedArray(source)) {
destination = new source.constructor(source);
} else if (!destination && isDate(source)) {
destination = new Date(source.getTime());
} else if (!destination && isRegExp(source)) {
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
} else {
var h = destination.$$hashKey;
if (isArray(destination)) {
destination.length = 0;
} else {
forEach(destination, function(value, key) {
delete destination[key];
});
if (isWindow(source) || isScope(source)) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
for (var key in source) {
if (source.hasOwnProperty(key)) {
destination[key] = copy(source[key], null, stackSource, stackDest);

destination = destination || Object.create(Object.getPrototypeOf(source));
traverse = true;
}

(stackSource || (stackSource = [])).push(source);
(stackDest || (stackDest = [])).push(destination);

if (traverse) {
if (isArray(source)) {
for (var i = 0, ii = destination.length = source.length; i < ii; i++) {
destination[i] = copy(source[i], null, stackSource, stackDest);
}
} else {
destinationHashKey = destinationHashKey || false;
for (var key in source) {
if (source.hasOwnProperty(key)) {
destination[key] = copy(source[key], null, stackSource, stackDest);
}
}
}
setHashKey(destination,h);
}
// Otherwise assign simple properties
} else if (!destination) {
destination = source;
}

if (destinationHashKey !== undefined) {
setHashKey(destination, destinationHashKey);
}

return destination;
}

Expand Down
1 change: 1 addition & 0 deletions test/AngularSpec.js
Expand Up @@ -428,6 +428,7 @@ describe('angular', function() {
expect(copy(null, {0:1,1:2,2:3})).toEqual({});
expect(copy(undefined, {0:1,1:2,2:3})).toEqual({});
expect(copy(new Date(), {0:1,1:2,2:3})).toEqual({});
expect(copy([4,5], {0:1,1:2,2:3})).toEqual({0:4,1:5,length:2});
expect(copy(/a/, {0:1,1:2,2:3})).toEqual({});
expect(copy(true, {0:1,1:2,2:3})).toEqual({});
});
Expand Down

0 comments on commit 8a2503b

Please sign in to comment.