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

Commit c9ccc80

Browse files
fix(copy): support copying properties with a null prototype
Partially cherry-picked from f7b9997
1 parent 420490a commit c9ccc80

File tree

2 files changed

+56
-10
lines changed

2 files changed

+56
-10
lines changed

Diff for: src/Angular.js

+41-10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
isUndefined: true,
3737
isDefined: true,
3838
isObject: true,
39+
isBlankObject: true,
3940
isString: true,
4041
isNumber: true,
4142
isDate: true,
@@ -172,6 +173,7 @@ var
172173
splice = [].splice,
173174
push = [].push,
174175
toString = Object.prototype.toString,
176+
getPrototypeOf = Object.getPrototypeOf,
175177
ngMinErr = minErr('ng'),
176178

177179
/** @name angular */
@@ -461,6 +463,16 @@ function isObject(value) {
461463
}
462464

463465

466+
/**
467+
* Determine if a value is an object with a null prototype
468+
*
469+
* @returns {boolean} True if `value` is an `Object` with a null prototype
470+
*/
471+
function isBlankObject(value) {
472+
return value !== null && typeof value === 'object' && !getPrototypeOf(value);
473+
}
474+
475+
464476
/**
465477
* @ngdoc function
466478
* @name angular.isString
@@ -742,7 +754,7 @@ function copy(source, destination, stackSource, stackDest) {
742754
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
743755
destination.lastIndex = source.lastIndex;
744756
} else if (isObject(source)) {
745-
var emptyObject = Object.create(Object.getPrototypeOf(source));
757+
var emptyObject = Object.create(getPrototypeOf(source));
746758
destination = copy(source, emptyObject, stackSource, stackDest);
747759
}
748760
}
@@ -761,7 +773,7 @@ function copy(source, destination, stackSource, stackDest) {
761773
stackDest.push(destination);
762774
}
763775

764-
var result;
776+
var result, key;
765777
if (isArray(source)) {
766778
destination.length = 0;
767779
for (var i = 0; i < source.length; i++) {
@@ -781,21 +793,40 @@ function copy(source, destination, stackSource, stackDest) {
781793
delete destination[key];
782794
});
783795
}
784-
for (var key in source) {
785-
if (source.hasOwnProperty(key)) {
786-
result = copy(source[key], null, stackSource, stackDest);
787-
if (isObject(source[key])) {
788-
stackSource.push(source[key]);
789-
stackDest.push(result);
796+
if (isBlankObject(source)) {
797+
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
798+
for (key in source) {
799+
putValue(key, source[key], destination, stackSource, stackDest);
800+
}
801+
} else if (source && typeof source.hasOwnProperty === 'function') {
802+
// Slow path, which must rely on hasOwnProperty
803+
for (key in source) {
804+
if (source.hasOwnProperty(key)) {
805+
putValue(key, source[key], destination, stackSource, stackDest);
806+
}
807+
}
808+
} else {
809+
// Slowest path --- hasOwnProperty can't be called as a method
810+
for (key in source) {
811+
if (hasOwnProperty.call(source, key)) {
812+
putValue(key, source[key], destination, stackSource, stackDest);
790813
}
791-
destination[key] = result;
792814
}
793815
}
794816
setHashKey(destination,h);
795817
}
796-
797818
}
798819
return destination;
820+
821+
function putValue(key, val, destination, stackSource, stackDest) {
822+
// No context allocation, trivial outer scope, easily inlined
823+
var result = copy(val, null, stackSource, stackDest);
824+
if (isObject(val)) {
825+
stackSource.push(val);
826+
stackDest.push(result);
827+
}
828+
destination[key] = result;
829+
}
799830
}
800831

801832
/**

Diff for: test/AngularSpec.js

+15
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,21 @@ describe('angular', function() {
370370
expect(copy(undefined, [1,2,3])).toEqual([]);
371371
expect(copy({0: 1, 1: 2}, [1,2,3])).toEqual([1,2]);
372372
});
373+
374+
it('should copy objects with no prototype parent', function() {
375+
var obj = extend(Object.create(null), {
376+
a: 1,
377+
b: 2,
378+
c: 3
379+
});
380+
var dest = copy(obj);
381+
382+
expect(Object.getPrototypeOf(dest)).toBe(null);
383+
expect(dest.a).toBe(1);
384+
expect(dest.b).toBe(2);
385+
expect(dest.c).toBe(3);
386+
expect(Object.keys(dest)).toEqual(['a', 'b', 'c']);
387+
});
373388
});
374389

375390
describe("extend", function() {

0 commit comments

Comments
 (0)