Skip to content

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

src/Angular.js

Lines changed: 41 additions & 10 deletions
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
/**

test/AngularSpec.js

Lines changed: 15 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy