【js手写】浅拷贝与深拷贝

1,617 阅读3分钟

深拷贝 浅拷贝

前言

大家对于拷贝这个词都不陌生吧,顾名思义就是copy,拷贝分为两种——深拷贝浅拷贝,实现这两种拷贝有很多种方式,本文来对它们做个总结。

正文

一、定义

浅拷贝:复制内存地址指针的操作,拷贝得到的对象受原对象的影响。
PS:(原始值类型不存在浅拷贝,只有引用类型有浅拷贝)

深拷贝:创建一个新的对象来承接原对象中的原始值,拷贝得到的对象不会受原对象的影响。

这里补充一个小小的知识点:js中数据类型分为基本数据类型引用数据类型

  • 基本数据类型:Number,String,Boolean,null,undefined,Symbol(ES6新增,表示独一无二的值),Bigint(比Number数据类型支持的范围更大的整数值,整数溢出不是问题)
  • 引用数据类型:Object,Array,Function,Date

二、实现

浅拷贝的实现

  1. 引用类型的赋值
let a={name:'aa'}  //对象为引用类型,放在堆里面
let b=a  //调用栈里a,b共享堆中对象的地址
a.name = 'bb'  //修改堆中对象的name的值
console.log(b.name);  //bb
  1. Object.create(a)
let a={name:'aa'}
let b=Object.create(a) //创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
a.name = 'bb'  //修改堆中对象的name的值
console.log(b.name);  //bb
  1. arr.concat( ) 拼接数组
  • 如果数组中都是基本数据类型,可以说是深拷贝
let arr=['old',1,true,null,undefined]
let newArr=arr.concat() //拼接了个空气进去,返回一个新数组
arr[0]='new'
console.log(newArr); //[ 'old', 1, true, null, undefined ]
  • 如果数组中有引用数据类型,那就是浅拷贝
let arr=[{n:'old'},1,true,null,undefined] 
let newArr=arr.concat()
arr[0].n='new'
console.log(newArr); //[ { n: 'new' }, 1, true, null, undefined ]
  1. arr.slice( ) 切割数组
let arr=[{n:'old'},1,true,null,undefined]
let newArr=arr.slice() //截取所有
arr[0].n='new'
console.log(newArr); //[ { n: 'new' }, 1, true, null, undefined ]

深拷贝的实现

JSON.parse(JSON.stringify(obj)):先将对象转为字符串,再转回对象

let arr=[{n:'old'},1,true,null,undefined]
let newArr=JSON.parse(JSON.stringify(arr))
arr[0].n='new'
console.log(newArr);//[ { n: 'old' }, 1, true, null, null ]

But,这种方法也有一定的局限

  • 不能拷贝undefined
  • 不能拷贝函数
  • 无法处理循环引用的情况
  • 不能拷贝Symbol类型

重点来了!面试官:手写一个深拷贝

思路:

  1. 将要拷贝对象的所有属性copy到新的对象中。对于原始值数据类型function不用特殊处理,因为函数没有子集,只需要处理Object对象的引用类型。

  2. 对于Object的引用数据类型可以分为对象数组,需要分别处理。

  3. 深拷贝的对象的引用类型的属性中仍可能包含引用类型,这里可以用递归来实现

实现:

let obj = {
  name: 'zt_ever',
  age: 18,
  like: {
    type: 'coding',
    num:{
      n:2
    }
  }
}

function deepCopy(obj){
  if (typeof obj !== 'object' || obj === null) return    //obj类型不为对象或为空
  let newObj = obj instanceof Array ? [] : {}     //obj类型为数组还是对象
  for(let key in obj){  //遍历对象的所有键名
    if(typeof obj[key]==='object'&&obj[key]!==null){  //obj[key]是对象且不为空
      //创建一个新的对象
      newObj[key]=deepCopy(obj[key])  //递归 创建的新对象赋给newObj[key]
    }
    else{
      newObj[key]=obj[key]//将键值赋给newObj
    }
  }
  return newObj
}

let newObj = deepCopy(obj)
console.log(newObj);  //{ name: 'zt_ever', age: 18, like: { type: 'coding', num: { n: 2 } } }

obj.age=20   //修改对象的值
obj.like.num.n=3
console.log(newObj);  //{ name: 'zt_ever', age: 18, like: { type: 'coding', num: { n: 2 } } }

总结

以上就是我对于深拷贝和浅拷贝的个人总结,如有错误欢迎评论指出~