深入了解JavaScript中的对象拷贝:浅拷贝与深拷贝


JavaScript中的对象拷贝:浅拷贝与深拷贝

JavaScript中的对象拷贝是开发中常见的操作,它允许我们创建原对象的副本,以便进行修改或传递而不影响原始对象,它只针对引用类型使用。在拷贝过程中,存在着浅拷贝和深拷贝两种方式,每种方式都有其优缺点和适用场景。

浅拷贝

浅拷贝是指创建一个新的对象或数组,复制原始对象或数组的基本属性或者元素,但是对于原始对象或数组中的引用类型属性(如对象、数组),仅复制其引用地址而不是实际内容。因此,当修改原对象或数组中的引用类型属性时,会影响到新对象或数组。

主要特点:

  1. 复制基本属性和元素: 浅拷贝会复制原始对象或数组的基本属性或元素,包括基本数据类型(如字符串、数字等)或一级深度的对象。
  2. 引用类型属性复制引用地址: 对于原对象或数组中的引用类型属性,浅拷贝仅复制其引用地址,而不是实际内容。
  3. 修改引用类型属性影响拷贝结果: 当修改原对象或数组中的引用类型属性时,会影响新对象或数组,因为它们共享相同的引用地址。

常见的浅拷贝方法:

  1. Object.create(obj) 创建一个新对象,并将现有对象作为新对象的原型(__proto__)。
  2. Object.assign({}, obj) 创建一个新对象,并将现有对象的属性和方法复制到新对象中。
  3. [].concat(arr) 创建一个新数组,将现有数组的元素复制到新数组中。
  4. 数组解构赋值: 可以用于数组的浅拷贝。

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()su 对象的属性复制到 t 目标对象中,最终返回了修改后的 newObjb 属性在 s 中的值覆盖了 t 中的值,而 cd 属性则是新增的。

另外:

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 等)以及性能方面需要更多的考虑。

相关推荐
喵叔哟32 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕2 小时前
Django 搭建数据管理web——商品管理
前端·python·django