对象属性深度合并与覆盖方法
对象属性深度合并与覆盖方法
需求
实现一个通用方法,要求
- 实现类似Object.assign()的属性覆盖功能,且当对象内部有重名子对象时,对重名的子对象也实现属性覆盖与合并
Object.assign()
Object.assign()实现浅拷贝
对于内部子对象时传递的引用
Object.assign()的定义与声明
Object.assign()源码
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
//不允许向空目标中合并
}
var to = Object(target);
//arguments 参数表,arguments[0]是target
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
用例1
const obj1 = {
a: 1,
b: 2,
}
const obj2 = {
a: 3,
c: 4,
}
const obj3 = Object.assign(obj1, obj2);
console.log(obj3)
//{ a: 3, b: 2, c: 4 }
用例2
const a = {
c: 2
}
const b = {
d: 3
}
const res = Object.assign(a, b);
console.log(res);
//{ c: 2, d: 3 }
a.c = 4;
b.d = 5;
console.log(res);
//{ c: 4, d: 3 }
可以初步察觉,Object.assign(a,b)返回的是a的浅拷贝
用例3
const obj1 = {
a: 1,
b: {
c: 2,
d: 4,
},
}
const obj2 = {
a: 3,
b: {
c: 3,
e: 5,
},
}
const obj3 = Object.assign(obj1, obj2);
console.log(obj3)
//{ a: 3, b: { c: 3, e: 5 } }
我希望在用例2中,合并obj1与obj2时可以得到{ a: 3, b: { c: 3, d:4,e: 5 } },即子对象也应用Object.assign()
实现
思路
使用与Object.assign类似的思路,将浅拷贝换成深拷贝,并对子对象进行递归拷贝覆盖
-
执行函数assign(target,orign),orign为覆盖的数据,target为被覆盖的数据
-
检查target和orign的类型
-
如果有一个是null类型,则返回orign的深拷贝
-
如果有一个是普通基础类型,则返回orign的深拷贝,否则进入下一步小步
-
如果有一个是array,返回orign的深拷贝
-
深拷贝target,存入到resp中
-
开始遍历orign中的key
- 如果resp也存在key,则进行递归,跳转到第1步,参数target=resp[key],orign=orign[key]
- 如果resp中不存在key属性,则直接合并
-
结束遍历返回resp
-
-
测试
用例1
const obj1 = {
a: 1,
b: {
c: 2,
d: 4,
},
}
const obj2 = {
a: 3,
b: {
c: 3,
e: 5,
},
}
const res1 = assign(obj1, obj2);
console.log(res1);//{ a: 3, b: { c: 3, d: 4, e: 5 } }
obj1.a = 4;
obj1.b.c = 5;
console.log(res1);//{ a: 3, b: { c: 3, d: 4, e: 5 } }
输出
{ a: 3, b: { c: 3, d: 4, e: 5 } }
{ a: 3, b: { c: 3, d: 4, e: 5 } }
用例2
const obj5 = {
a: 2,
b: {
f: 2,
g: {
h: [4, 5],
i: {
a: 123
}
},
x: 13
},
c: [1, 2, 3],
d: false,
f: 5,
g: { a: 1 },
h: null,
i: { a: 3 },
k: {}
}
const obj6 = {
a: {
e: 3
},
b: {
f: 20,
g: {
h: [41, 5],
i: {
a: 1234
}
},
y: 14
},
c: [2],
d: true,
f: [1, 23],
g: [2, 3],
h: { a: 3 },
i: null,
k: { a: 1 },
l: [4, 5]
}
const res3 = assign(obj5, obj6);
console.log(JSON.stringify(res3));
//{"a":{"e":3},"b":{"f":20,"g":{"h":[41,5],"i":{"a":1234}},"x":13,"y":14},"c":[2],"d":true,"f":[1,23],"g":[2,3],"h":{"a":3},"i":null,"k":{"a":1},"l":[4,5]}
obj5.c.push(1)
obj6.c.push(3)
obj6.f.push(4)
obj6.l.push(8)
console.log(JSON.stringify(res3));
//{"a":{"e":3},"b":{"f":20,"g":{"h":[41,5],"i":{"a":1234}},"x":13,"y":14},"c":[2],"d":true,"f":[1,23],"g":[2,3],"h":{"a":3},"i":null,"k":{"a":1},"l":[4,5]}
输出
{"a":{"e":3},"b":{"f":20,"g":{"h":[41,5],"i":{"a":1234}},"x":13,"y":14},"c":[2],"d":true,"f":[1,23],"g":[2,3],"h":{"a":3},"i":null,"k":{"a":1},"l":[4,5]}
{"a":{"e":3},"b":{"f":20,"g":{"h":[41,5],"i":{"a":1234}},"x":13,"y":14},"c":[2],"d":true,"f":[1,23],"g":[2,3],"h":{"a":3},"i":null,"k":{"a":1},"l":[4,5]}
可以看见,在上述的两个测试用例中,assign()函数都很好的达成了我需要的功能,并且返回的也是一个新对象,避免了返回对象引用带来的问题。
当然,由于频繁的进行深拷贝,和普通的Object.assign()相比,我封装的assign显然更加耗能。
源码
/**
*
* @param target 目标对象 被覆盖的对象
* @param orign 源对象 覆盖的对象
* @returns
*/
function assign(target: any, orign: any) {
if (orign === null || target === null) return JSON.parse(JSON.stringify(orign))
if (typeof target === 'object' && typeof orign === 'object') {
if (Array.isArray(orign) || Array.isArray(target)) {
return JSON.parse(JSON.stringify(orign))
}
//深拷贝target
let resp = JSON.parse(JSON.stringify(target));
for (let key in orign) {
if (key in resp) {
resp[key] = assign(resp[key], orign[key]);
} else {
//如果resp中不存在key属性,则直接合并
resp[key] = JSON.parse(JSON.stringify(orign[key]))
}
}
return resp
} else {
//如果有一个是基础类型,则直接返回第二个参数替换
return JSON.parse(JSON.stringify(orign))
}
}
进阶
前面实现了将一个对象合并到另一个对象上的方法,现在来实现将多个对象合并到同一对象上的方法
思路
同样类似于Object.assign(),使用参数表来进行一个遍历合并的操作
测试
const obj1 = {
a: 1,
b: {
c: 2,
d: 4,
},
}
const obj2 = {
a: 2,
b: {
c: 3,
e: 5,
f: 6,
},
}
const obj3 = null
const obj4 = {
a: 3,
b: {
c: 4,
d: 5,
f: 7,
}
}
const resp = assign(obj1, obj2, obj3, obj4)
console.log(resp)//{ a: 3, b: { c: 4, d: 5, e: 5, f: 7 } }
输出
{ a: 3, b: { c: 4, d: 5, e: 5, f: 7 } }
源码
/**
*
* @param target 目标对象 被覆盖的对象
* @param orign 源对象 覆盖的对象
* @returns
*/
function func(target: any, orign: any) {
if (orign === null || target === null) return JSON.parse(JSON.stringify(orign))
if (typeof target === 'object' && typeof orign === 'object') {
if (Array.isArray(orign) || Array.isArray(target)) {
return JSON.parse(JSON.stringify(orign))
}
//深拷贝target
let resp = JSON.parse(JSON.stringify(target));
for (let key in orign) {
if (key in resp) {
resp[key] = func(resp[key], orign[key]);
} else {
//如果resp中不存在key属性,则直接合并
resp[key] = JSON.parse(JSON.stringify(orign[key]))
}
}
return resp
} else {
//如果有一个是基础类型,则直接返回第二个参数替换
return JSON.parse(JSON.stringify(orign))
}
}
/**
*
* @param target 目标对象 被覆盖的对象
* @param others 源对象 覆盖的对象
* @returns
*/
function assign(target: any, ...others: any) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
//不允许向空目标中合并
}
let resp = JSON.parse(JSON.stringify(target))
for (let index = 1; index < arguments.length; index++) {
let nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
resp = func(resp, nextSource)
}
}
return resp
}
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果