前言
在JavaScript中,深拷贝和浅拷贝是常用的对象拷贝(复制)的方式。深拷贝创建一个新对象,并将原始对象的所有属性和嵌套对象都复制到新对象中。与之相反,浅拷贝只复制原始对象的属性的引用而不是属性的值。下面我将详细介绍深拷贝和浅拷贝,以及实现手写一个浅拷贝和深拷贝的方法。
浅拷贝
浅拷贝是指创建一个新对象,新对象的属性与原始对象相同,但属性引用的对象还是指向原始对象引用的对象。换句话说,浅拷贝只复制了对象的引用,而不是对象本身。当原始对象的属性是基本数据类型时,浅拷贝对属性值进行拷贝。但当原始对象的属性是引用类型时,浅拷贝只拷贝了引用,并没有创建新的对象。
通过对浅拷贝概念的理解,要实现其原理,无非就是创建一个新对象,然后通过遍历将原始对象的属性全部拿到新对象里面。所以问题就是这个拿该如何实现呢?那我们先来了解一下for in
方法。
for in
for in
方法既可以用来遍历数组,也可以遍历对象。
当for in
遍历数组时,这里形参key
相当于数组的每一项下标,arr[key]
就是arr
数组中的每一项值。
js
const arr = ['1', '2', '3', '4', '5', '6']
for (const key in arr) {
console.log(key); //依次打印数组每一项下标0,1,2,3,4,5
}
for(const key in arr) {
console.log(arr[key]); //依次打印数组每一项1,2,3,4,5,6
}
arr['like'] = 'writing' //给arr数组添加一个下标为like,值为writing
for (const key in arr) {
console.log(key); //依次打印数组每一项下标0,1,2,3,4,5,like
}
for(const key in arr) {
console.log(arr[key]); //依次打印数组每一项1,2,3,4,5,6,writing
}
for in
用来遍历对象,就和遍历数组一样的,这里形参key
为对象obj
的每一项属性名,obj[key]
为obj
对象的每一项属性名的值,特别注意obj.key
不是获取obj
每一项的值,这里的key
会被当成obj
的一个属性,而不是变量 ,所以obj.key
相当于获取obj
上一个名为key
的属性的值。
js
const obj = {
name:'小米',
age:18,
like:{
sport:'running'
}
}
//给obj对象添加一个新的属性sex,值为'男'
obj['sex'] = '男'// 等价于obj.sex = '男'
for (const key in obj) {
console.log(key); //依次打印name age like sex,即对象的各属性名
}
for (const key in obj) {
console.log(obj.key); //依次打印undefined undefined undefined undefined
//注:不能这样遍历对象的值,key会被当成是obj的一个属性
}
for (const key in obj) {
console.log(obj[key]); //依次打印 小米 18 { sport: 'running' } 男,遍历对象每一项
//就是和遍历数组一样的,对象名[属性名]
}
上面的对象遍历相信大家已经完全理解了,那如果我创建的对象隐式继承了一些属性,那使用for in
方法来遍历结果还是一样的吗?我们一起来看一看:
js
const obj = {
name:'小米',
age:18,
like:{
sport:'running'
}
}
//创建obj2空对象,隐式继承obj对象上的属性
const obj2 = Object.create(obj)
//给obj2添加一个属性sex,值为'男'
obj2.sex = '男'
console.log(obj2) // { sex: '男' }
for(const item in obj2) {
console.log(item) //依次打印sex name age like
//将obj2隐式原型上的属性也遍历出来了
}
可以看到使用for in
方法遍历对象,会将其隐式原型上的属性也遍历到。很显然,这种结果并不是我们想要的,所以我们该如何解决这种情况呢?我们继续看代码:
js
const obj = {
name:'小米',
age:18,
like:{
sport:'running'
}
}
const obj2 = Object.create(obj) //创建obj2空对象,隐式继承obj对象上的属性
obj2.sex = '男' //给obj2添加一个属性sex,值为'男'
console.log(obj2) // { sex: '男' }
for(const item in obj2) {
//使用data.hasOwnProperty()方法,用来判断对象上的属性是不是隐式原型上的属性
if(obj2.hasOwnProperty(item)) //是隐式原型上的属性,返回false,不是返回true
console.log(item) //只打印sex
}
在for in
方法里面通过data.hasOwnProperty()
方法来判断,遍历的属性是不是该对象的隐式原型上的属性,再加一个if判断就解决了for in
会把对象隐式原型上的属性也遍历出来的问题啦~
看到这里,相信大家已经对for in
方法已经完全掌握啦!那接下来,我们实现手写浅拷贝的原理就非常简单啦~
手写浅拷贝方法
开始我们提到了,要实现浅拷贝,无非就是创建一个新对象,然后通过遍历将原始对象的属性全部拿到新对象里面。那我们通过for in
方法去遍历原始对象的每一项,将其值赋值给新对象是不是就可以实现啦~看看代码:
js
//浅拷贝的实现原理
const obj = {
name:'啊纬',
age:18,
like:{
type:'coding'
}
}
function shalldowCopy(obj){
const objCopy = {}
for(const key in obj){
if(obj.hasOwnProperty(key)){ //不需要obj隐式原型上的属性
//每次给新对象添加原对象的一个属性和属性值
objCopy[key] = obj[key]
}
}
return objCopy
}
const newobj = shalldowCopy(obj)
console.log(newobj) //{ name: '啊纬', age: 18, like: { type: 'coding' } }
到这里浅拷贝的核心代码其实就实现啦,当然还要添加一些细节,要对参数进行判断,上面的参数我们只考虑了它是对象,如果不是对象或者是null(null的类型判断是对象) ,我们就得直接返回。当然,数组也是对象,所以我们也要判断对象是不是数组,如果是数组,则将objCopy
创建为数组,否则,创建为对象。那我们再更新一下代码:
js
//浅拷贝的实现原理
const obj = {
name:'啊纬',
age:18,
like:{
type:'coding'
}
}
const arr = ['a', {n:1}, 1, undefined , null]
function shalldowCopy(obj){
if(typeof obj !== 'object' || obj == null) return //不是对象或者是null直接返回
const objCopy = obj instanceof Array ? [] : {} //判断obj是数组还是对象
for(const key in obj){
if(obj.hasOwnProperty(key)){ //不需要obj隐式原型上的属性
//每次给新对象添加原对象的一个属性和属性值
objCopy[key] = obj[key]
}
}
return objCopy
}
console.log(shalldowCopy(obj)) //{ name: '啊纬', age: 18, like: { type: 'coding' } }
console.log(shalldowCopy(arr)) //[ 'a', { n: 1 }, 1, undefined, null ]
好~到这里我们就成功实现手写了一个浅拷贝的方法啦!是不是感觉很简单呢😁,下面我们再来看看深拷贝怎么实现叭。
深拷贝
相比之下,深拷贝是指创建一个新对象,并将原始对象的所有属性都复制到新对象中,不论这些属性是基本数据类型还是引用类型。深拷贝不仅复制了对象的引用,还创建了与原始对象属性引用的对象相同的新对象。因此,深拷贝得到的是一个全新的独立对象。
手写深拷贝方法
其实,在我们实现浅拷贝方法的时候就可以发现,我们给新对象赋值的时候,当原始对象的属性是基本数据类型时,浅拷贝对属性值进行赋值拷贝,但当原始对象的属性是引用类型时,浅拷贝赋值只拷贝了引用,并没有创建新的对象。所以,这也是为什么通过浅拷贝出来的新对象,改变原始对象的引用类型数据,新对象的引用类型数据也会跟着改变的原因所在了。
那要实现深拷贝,是不是只要当原始对象的属性是引用类型时,我们能得到一个和它有相同属性的新对象就可以啦~那我们就可以这样想了,我们实现浅拷贝的时候,就是将原对象所有属性都赋值到了新对象,然后将新对象返回出来了,欸,这个返回的对象是不是就是一个新对象,那我们是不是可以这样,在将原始对象的属性拷贝到新对象的时候,判断它是不是引用类型,如果不是,则直接添加到新对象里面,如果是,那就将该引用类型的属性作为参数再次调用拷贝方法,待它被拷贝完成后,是不是就会返回了一个新的对象。这样是不是就实现了将原始对象的所有属性都复制到新对象中,并且当原始对象的属性是引用类型时,创建了一个新的对象拷贝了里面的属性值。那我们来看看代码如何实现:
js
//手动实现深拷贝
const obj = {
name:'啊纬',
age:18,
like:{
type:'coding'
}
}
function deepCopy(obj) {
const objCopy = {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if(obj[key] instanceof Object){ //判断obj中的属性是不是引用类型
objCopy[key] = deepCopy(obj[key]) //是引用类型,将该属性作为参数再次调用拷贝方法
//返回一个新对象后再赋值回objCopy[key]
}
else{
objCopy[key] = obj[key] //不是引用类型直接赋值
}
}
}
return objCopy
}
const res = deepCopy(obj)
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
obj.like.type = 'sleeping'
//原对象引用类型数据改变,深拷贝得到的对象数据不发生改变
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
这样深拷贝的核心代码我们就成功实现啦~当然这里的代码优化和浅拷贝一样:
js
//手动实现深拷贝
const obj = {
name:'啊纬',
age:18,
like:{
type:'coding'
}
}
function deepCopy(obj) {
if(typeof obj !== 'object' || obj == null) return //不是对象或者是null直接返回
const objCopy = obj instanceof Array ? [] : {} //判断obj是数组还是对象
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if(obj[key] instanceof Object){ //判断obj[key]是不是引用类型
objCopy[key] = deepCopy(obj[key])
}
else{
objCopy[key] = obj[key]
}
}
}
return objCopy
}
const res = deepCopy(obj)
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
obj.like.type = 'sleeping'
console.log(res) // { name: '啊纬', age: 18, like: { type: 'coding' } }
总结
如果觉得文章对您有所帮助的话,麻烦给小米露点点关注,点点赞咯😘,有问题欢迎各位小伙伴评论喔😊~
这是本人的gitee仓库地址:gitee.com/xiaomi-dew/... 。写的各项目代码源码,学习代码和各种资源都在里面啦~如果觉得还不错的话,可以给我的小仓库也点点Star喔😍😍😍 ~