六个方法两个拷贝带你透彻对象合并问题

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

实际场景下,我们经常需要进行对象合并的操作,而有时候原对象的改变会改变合并后的对象,这是一个非常严重的问题,涉及到了合并对象中的深浅拷贝,递归遍历,让人发蒙。

针对这个问题,我做了一个整理,下面将列举对象合并的3种形式共计6种方法,分别为Object.assign()扩展运算符第三方库合并方法,并对Object.assign()进行重点介绍。

Object.assign()

下面围绕使用三个关键性问题来带读者掌握Object.assign()

使用

Object.assign()可以将要被合并的对象的所有可枚举属性浅拷贝到合并目标对象,这里简单解释一下可枚举属性:可以被for…in遍历得到的属性为可枚举属性

let header = {
    name:"猪痞恶霸"
}
let body = {
    age:20
}
let person = {}
Object.assign(person,header,body) // {name: '猪痞恶霸', age: 20}

我声明了headerbody作为被合并的对象,再声明一个要合并的目标对象,通过Object.assign()合并得到最终的合并对象

合并冲突问题

那么肯定有这么一种情况,我们的被合并对象之间或者与合并目标对象之间存在相同的属性,这种情况得到的结果是怎么样的

let ned = {
    name:"Ned"
}
let hogskin = {
    name:"猪痞恶霸"
}
let person = {
    name:"fzf404"
}
Object.assign(person,ned,hogskin) // {name: '猪痞恶霸'}

我声明了三个相同的对象,以person为目标,最后得到的结果为{name: '猪痞恶霸'},所以说如果合并的对象中存在同名属性,则后面的属性会覆盖前面的属性,这里就是hogskin对象的name属性

浅拷贝问题

Object.assign()是一个浅拷贝方法,同俗解释一下在这个场景的浅拷贝问题,合并对象的时候,如果被合并对象的属性有引用类型即对象或者数组,在合并后就会出现一个问题,合并后对象的引用类型属性会随着被合并对象的引用类型属性的改变而改变。

let bun = {
    info:{
        name:"战场小包",
        age:"未知"
    }
}
let person = {}
let obj = Object.assign(person,bun)
console.log(obj) // info: {name: '战场小包', age: '未知'}
bun.info.name = "Ned"
console.log(obj) // info: {name: 'Ned', age: '未知'}

如上,我们声明bun对象内有一个对象属性info,当bun.info.name改变其属性值,我们合并的对象内的属性值也随着改变,这就是浅拷贝问题,因为浅拷贝针对于引用类型,复制的是其引用地址,地址对应的空间内是可以改变的。

参数问题

关于参数有两个问题,一个问题是当只有一个参数的时候会返回什么

let obj = {}
Object.assign(obj) // {}

如果参数只有一个的情况下,其会返回原参数,如上我声明一个空对象,传入Object.assign()中返回obj{}

还有一个问题就是当只传入一个参数,且传入参数不是对象的情况,会发生什么

Object.assign(4) // Number {4}

这种情况,那么就会将传入的参数转换为对象的形式,我传入了一个数值4,那么就返回了一个对象Number {4}

那么如果参数转换不成对象,比如undefined又或者null,那么就会出现错误

Object.assign(null) // Cannot convert undefined or null to object

报错也很直观明了:Cannot convert undefined or null to object,如果不可被转换成对象的undefinednull或者其他非对象作为参数出现在非首位,那么就会将这些参数跳过,不会被合并到目标对象中,当然也不会出现错误情况

let obj = {}
let num = 404
let person = {
    name:"猪痞恶霸"
}
Object.assign(obj,num,person) // {name: '猪痞恶霸'}

上面的结果就是将str参数跳过,合并person对象

这个时候应该联想到一个类型,那就是字符串,它比较特殊,可以被转换为类数组对象并合并进目标对象内

let obj = {}
let str = "猪痞恶霸" 
Object.assign(obj,str) // {0: '猪', 1: '痞', 2: '恶', 3: '霸'}

我们看到结果就是一个非常典型的类数组对象,键值为数字。

Object.assign()就学习到这里,我们下面来看看另外两个合并对象的方法

扩展运算符

对象的扩展运算符可以用来合并对象,使用{...a,...b}的形式来合并

let head = {
    name:"fzf404"
}
let body = {
    age:20
}
let obj = {...body,...head}
console.log(obj) // {age: 20, name: 'fzf404'}

上面的let obj = {...body,...head}也等同于Object.assign(obj,body,head)

第三方库

第三方库中有很多对象合并的方法,可以解决很多问题,下面我将介绍lodash库中的assign()merge()defaultesDeep,相关例子大家可以去官方文档查询:[官方文档]

assign()

assign()Object.assign()是相同的,这里不再赘述,主要来看merge()defaultsdefaultesDeep()

merge()

merge()assign()最大的不同是它合并是靠递归遍历对象属性,所以merge()可以深拷贝合并对象,也就是说引用类型的改变不会影响到合并得到得对象。

defaults()

defaults()assign()相似,只不过在参数合并冲突的解决方式上有区别,前面的属性不为undefined且与后面属性名相同,后面的对象属性会被忽略

defaultsDeep()

defaultsDeep()方法与defaults()对应,唯一区别是其是递归遍历,所以可以深拷贝合并对象

最后

数一数,我了解的大概也就6种方法,其中掌握深拷贝浅拷贝合并,以及Object.assign()的使用细节就可以了,我们常常会遇到对象合并的需求,而合并中也经常会遇到浅拷贝和深拷贝问题,而我们常常需要深拷贝,这个时候可以使用第三方库或者自己封装一个深拷贝合并函数。

猜你喜欢

转载自juejin.im/post/7126688588082708488