JavaScript中的对象拷贝:浅拷贝与深拷贝
JavaScript中的对象拷贝是开发中常见的操作,它允许我们创建原对象的副本,以便进行修改或传递而不影响原始对象,它只针对引用类型使用。在拷贝过程中,存在着浅拷贝和深拷贝两种方式,每种方式都有其优缺点和适用场景。
浅拷贝
浅拷贝是指创建一个新的对象或数组,复制原始对象或数组的基本属性或者元素,但是对于原始对象或数组中的引用类型属性(如对象、数组),仅复制其引用地址而不是实际内容。因此,当修改原对象或数组中的引用类型属性时,会影响到新对象或数组。
主要特点:
- 复制基本属性和元素: 浅拷贝会复制原始对象或数组的基本属性或元素,包括基本数据类型(如字符串、数字等)或一级深度的对象。
- 引用类型属性复制引用地址: 对于原对象或数组中的引用类型属性,浅拷贝仅复制其引用地址,而不是实际内容。
- 修改引用类型属性影响拷贝结果: 当修改原对象或数组中的引用类型属性时,会影响新对象或数组,因为它们共享相同的引用地址。
常见的浅拷贝方法:
Object.create(obj)
: 创建一个新对象,并将现有对象作为新对象的原型(__proto__
)。Object.assign({}, obj)
: 创建一个新对象,并将现有对象的属性和方法复制到新对象中。[].concat(arr)
: 创建一个新数组,将现有数组的元素复制到新数组中。- 数组解构赋值: 可以用于数组的浅拷贝。
Object.create(obj)
使用Object.create(obj)
方法会会继承原对象的原型链,创建一个新对象,使用现有的对象来提供新创建的对象的__proto__;因此此方法常用于创建具有特定原型的新对象,通过指定原型对象,可以实现对象之间的原型链继承,使得新创建的对象能够共享原型对象的属性和方法。
如下列代码:
css
let a = {
name: 'XM'
}
let b = Object.create(a) //隐式继承 浅拷贝
a.name = 'XH'
console.log(b.name); //XH
在上述代码中,Object.create(a)
创建了一个新对象 b
,它继承了a
对象的原型链,因此能够访问a
,对象中定义的name
属性。
注意 :传入的参数不是一个对象或是 null
,会抛出 TypeError
错误。
Object.assign({}, obj)
Object.assign()
方法是用于将一个或多个原对象的属性复制到目标对象中,并返回目标对象。该方法会修改目标对象,若目标对象已有相同属性,则会覆盖原有属性。
与Object.create(obj)
方法不同的是,Object.assign()
方法不会继承原对象的原型链。
如下列代码所示:
ini
const t={ a: 1, b: 2 };
const s = { b: 3, c: 4 };
const u = { d: 5 };
const newObj = Object.assign(t, s, u);
console.log(newObj); // 输出: { a: 1, b: 3, c: 4, d: 5 },目标对象被修改
在上述示例中,Object.assign()
将 s
、u
对象的属性复制到 t
目标对象中,最终返回了修改后的 newObj
。b
属性在 s
中的值覆盖了 t
中的值,而 c
和 d
属性则是新增的。
另外:
css
let a={
like: {
n: 'coding'
}
}
let b = Object.assign(a,b)
a.like.n = 'music'
console.log(b.like.n); //music 拷贝了like对象的引用地址,所以b.like.n也会被修改
let c = Object.assign({}, a, b) //使用此方法会将b放到a里面,再将a放到{}里面,a不会被修改
上述代码中,进一步说明了使用Object.assign()
方法时,会拷贝原对象的引用地址,当地址中的元素改变时,新对象的相应元素也会发生改变。当使用Object.assign({}, a, b)
拼接对象时,当原对象中元素改变时,不会改变a中的元素。
[].concat(arr)
当使用 [].concat(arr)
方法进行浅拷贝时,它可以用于复制一个数组的元素到一个新的数组中,从而创建一个新的数组对象。这是数组的浅拷贝使用用法,不会继承原数组的原型链,并且concat()
方法不会修改原始数组,而是返回一个新数组。。
如下列代码所示:
ini
let arr = [1, [2, 3],{n: 10}]
let newArr = [].concat(arr) //浅拷贝 创建了一个新数组
arr[3].n = 100
console.log(newArr); //[ 1, 2, 3, { n: 100 } ]
上述代码中,[].concat(arr)
创建了一个新的数组 newArr
,包含了 arr
的所有元素。但需要注意的是,如果原数组中存在嵌套数组,concat()
方法只会复制嵌套数组的引用地址到新数组中,因此在修改原数组中的嵌套数组元素时,新数组中的嵌套数组元素也会受到影响。
数组解构赋值
数组解构赋值可以用于复制数组的元素到一个新数组中,从而实现浅拷贝的效果。
如下列代码:
scss
let arr = [1, [2, 3],{n: 10}]
let newArr = [...arr] //浅拷贝
arr.push(4) //往arr里面添加元素,newArr不会被修改
console.log(newArr); //[ 1, [2, 3], { n: 10 } ]
上述代码中,通过使用[...arr]
进行数组解构赋值,创建了一个新的数组newArr
。当修改原始数组中的元素时,新数组不受影响,因为它们是两个不同的数组。需要注意的是,当处理嵌套数组时,嵌套数组的解构会生成一个新的嵌套数组,但其中的引用类型元素仍然保持引用关系。
浅拷贝的实现方法
javascript
let obj = {
name: 'TM',
age: 21,
like: {
n: 'coding'
}
}
function shallowCopy(obj) {
// 不是引用类型就不拷贝
if(typeof obj !== 'object' || obj === null) return
// 如果形参obj是数组,就创建一个新数组,如果是对象,就创建一个新对象
// if (Array.isArray(obj)) {
// let objCopy = []
// } else {
// let objCopy = {}
// }
let objCopy = obj instanceof Array ? [] : {}
// obj instanceof Array ? [] : {} 是一个条件(三元)运算符,
// 它会检查 obj 是否是一个数组。如果 obj 是一个数组,那么就创建一个新的空数组[],否则创建一个新的空对象 { }。
// 遍历obj,将obj的属性赋值给objCopy
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
objCopy[key] = obj[key]
// 如果是数组 onjCopy[0]=obj[0]
// 如果是对象 objCopy.name=obj.name
}
}
return objCopy
}
let newObj = shallowCopy(obj)
obj.like.n = 'music'
console.log(newObj); //{ name: 'TM', age: 21, like: { n: 'music' } }
深拷贝
深拷贝是指创建一个新的对象或数组,复制原始对象或数组的所有内容,包括基本属性、引用类型属性(对象、数组等)以及嵌套的对象或数组内容。深拷贝不会受到原始对象修改的影响,它会完全复制所有嵌套对象的内容,确保新对象与原对象完全独立。
深拷贝的方法:
JSON.parse(JSON.stringify(obj))
利用 JSON.stringify()
将原对象转换成 JSON 字符串,再用 JSON.parse()
将 JSON 字符串转换成新的对象。这种方法会忽略undefined、symbol函数,会抛弃对象的constructor,也就是深拷贝之后,不管这个对象原本的构造函数是什么,在深拷贝之后都会变成Object。需要注意的是,此方法不能处理undefined、symbol、function这些数据类型,也无法处理循环引用。
如下列代码:
yaml
let obj = {
name: 'TM',
age: 18,
a: {
n: 1
},
b: undefined,
c: null,
d: function () { },
e: Symbol('hello'),
f: {
n: 100
}
}
let obj2 = JSON.parse(JSON.stringify(obj)); // 深拷贝
obj.age = 20;
obj.a.n = 2
console.log(obj2); // { name: 'TM', age: 18, a: { n: 1 }, c: null, f: { n: 100 } }
如上述代码所示,使用JSON.parse(JSON.stringify(obj))
将转换后的对象拷贝给obj2
,即使更改obj
的元素后,obj2
中的元素也没有发生改变,因此深拷贝不会受到原始对象修改的影响。
深拷贝的实现方法
在js中实现深拷贝,可以使用递归调用的方法实现
javascript
let obj = {
name: 'TM',
age: 18,
a: {
n: 1
},
b: undefined,
c: null,
d: function () { },
e: Symbol('hello'),
f: {
n: 100
}
}
function deepCopy(obj) {
let objCopy = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//区分obj[key]是原始类型或者引用类型
if (obj[key] instanceof Object) {// 不能直接赋值
objCopy[key] = deepCopy(obj[key]) // 递归调用
} else {
objCopy[key] = obj[key]
}
}
}
return objCopy
}
let obj2 = deepCopy(obj)
obj.age = 20;
console.log(obj2);
结语
在大部分情况下浅拷贝都能满足我们的需求,但对于嵌套的引用类型属性,需要特别小心,因为它们共享相同的引用地址。相比之下,深拷贝能够完全复制所有的嵌套内容,但在处理循环引用、特殊类型(如函数、undefined 等)以及性能方面需要更多的考虑。