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 all 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
1 change: 1 addition & 0 deletions src/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"nextUid": false,
"setHashKey": false,
"extend": false,
"merge": false,
"int": false,
"inherit": false,
"noop": false,
Expand Down
60 changes: 46 additions & 14 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,30 @@ function setHashKey(obj, h) {
}
}


function baseExtend(dst, objs, deep) {
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
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];
if (key === '$$hashKey') continue;
var src = obj[key];

if (deep && isObject(src)) {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
} else {
dst[key] = src;
}
}
}

return dst;
}


/**
* @ngdoc function
* @name angular.extend
Expand All @@ -333,28 +357,36 @@ 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).
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var h = dst.$$hashKey;
return baseExtend(dst, slice.call(arguments, 1), false);
}

for (var i = 1, ii = arguments.length; i < ii; 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];
}
}
}

setHashKey(dst, h);
return dst;
/**
* @ngdoc function
* @name angular.merge
* @module ng
* @kind function
*
* @description
* Deeply 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.merge({}, object1, object2)`.
*
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
* objects, performing a deep copy.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function merge(dst) {
return baseExtend(dst, slice.call(arguments, 1), true);
}

function int(str) {
Expand Down
1 change: 1 addition & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function publishExternalAPI(angular) {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'merge': merge,
'equals': equals,
'element': jqLite,
'forEach': forEach,
Expand Down
1 change: 1 addition & 0 deletions test/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"nextUid": false,
"setHashKey": false,
"extend": false,
"merge": false,
"int": false,
"inherit": false,
"noop": false,
Expand Down
68 changes: 68 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,74 @@ describe('angular', function() {
});
});


describe('merge', function() {
it('should recursively copy objects into dst from left to right', function() {
var dst = { foo: { bar: 'foobar' }};
var src1 = { foo: { bazz: 'foobazz' }};
var src2 = { foo: { bozz: 'foobozz' }};
merge(dst, src1, src2);
expect(dst).toEqual({
foo: {
bar: 'foobar',
bazz: 'foobazz',
bozz: 'foobozz'
}
});
});


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


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


it('should copy references to functions by value rather than merging', function() {
function fn() {}
var dst = { foo: 1 };
var src = { foo: fn };
merge(dst, src);
expect(dst).toEqual({
foo: fn
});
});


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


describe('shallow copy', function() {
it('should make a copy', function() {
var original = {key:{}};
Expand Down