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

feat(extend): optionally deep-extend when last parameter is true #10519

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,27 +333,43 @@ function setHashKey(obj, h) {
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
* Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @param {boolean=} deep if the last parameter is set to `true`, objects are recursively merged
* (deep copy). Defaults to `false`.
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var h = dst.$$hashKey;
var argsLength = arguments.length;
var isDeep = false;
if (argsLength >= 3) {
var maybeIsDeep = arguments[argsLength - 1];
// Secret code to use deep extend without adding hash keys to destination object properties!
if (maybeIsDeep === true || maybeIsDeep === 0xFACECAFE) isDeep = maybeIsDeep;
}

if (isDeep) --argsLength;

for (var i = 1, ii = arguments.length; i < ii; i++) {
for (var i = 1; i < argsLength; i++) {
var obj = arguments[i];
if (obj) {
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
dst[key] = obj[key];
if (!isObject(obj) && !isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];

if (isDeep && isObject(src)) {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
extend(dst[key], src, 0xFACECAFE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about object cycles? At least, I think we should put something in the docs about not trying to deep extend an object that has cyclic references.

} else {
dst[key] = src;
}
}
}

setHashKey(dst, h);
if (isDeep !== 0xFACECAFE) setHashKey(dst, h);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably still attach a hashkey if h is defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think that makes sense, will get rid of FACECAFE =)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich bin bis 05.01.2015 abwesend.

Ich werde vertreten durch Herrn T. Landgraf und Herrn D. Neander.

Hinweis: Dies ist eine automatische Antwort auf Ihre Nachricht "Re:
[angular.js] feat(extend): optionally deep-extend when last parameter is
true (#10519)" gesendet am 20.12.2014 03:58:31.

Diese ist die einzige Benachrichtigung, die Sie empfangen werden, während
diese Person abwesend ist.=

return dst;
}

Expand Down
63 changes: 63 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,69 @@ describe('angular', function() {
// make sure we retain the old key
expect(hashKey(dst)).toEqual(h);
});

it('should perform deep extend when last argument is true', function() {
var src = { foo: { bar: 'foobar' }},
dst = { foo: { bazz: 'foobazz' }};
extend(dst, src, true);
expect(dst).toEqual({
foo: {
bar: 'foobar',
bazz: 'foobazz'
}
});
expect(dst.foo.$$hashKey).toBeUndefined();
});


it('should replace primitives with objects when deep extending', function() {
var dst = { foo: "bloop" };
var src = { foo: { bar: { baz: "bloop" }}};
extend(dst, src, true);
expect(dst).toEqual({
foo: {
bar: {
baz: "bloop"
}
}
});
});


it('should replace null values with objects when deep extending', function() {
var dst = { foo: null };
var src = { foo: { bar: { baz: "bloop" }}};
extend(dst, src, true);
expect(dst).toEqual({
foo: {
bar: {
baz: "bloop"
}
}
});
});


it('should not deep extend function-valued sources when deep extending', function() {
function fn() {}
var dst = { foo: 1 };
var src = { foo: fn };
extend(dst, src, true);
expect(dst).toEqual({
foo: fn
});
});


it('should create a new array if destination property is a non-object and source property is an array when deep-extending', function() {
var dst = { foo: NaN };
var src = { foo: [1,2,3] };
extend(dst, src, true);
expect(dst).toEqual({
foo: [1,2,3]
});
expect(dst.foo).not.toBe(src.foo);
});
});

describe('shallow copy', function() {
Expand Down