详解js深浅复制

本文深入解析JavaScript中的深浅复制概念,区分基本数据类型与引用数据类型的复制差异,介绍浅复制的局限性及深复制的实现方法。

文章转自:https://segmentfault.com/a/1190000008838101

作者写的很好! 赞 !

前言

在之前写继承的过程谈到了深浅复制的问题,因为有读者反映到需要解析,趁今天周末写一篇解析,今天的主体相对之前来说理解难度低一些,篇幅可能也比较短,诸君按需阅读即可。

从两种数据类型说起

在js中,变量的类型可以大致分成两种:基本数据类型和引用数据类型,其中基本数据类型指的是简单的数据段,包括:

  • Underfied
  • Null
  • Boolean
  • Number
  • String(字符串在一些其他语言中是被当做对象使用的,属于引用类型,但在js里是基本类型)

而引用类型的值指的是可能包含多个值的对象。可能上面这种描述大家都看过不少,但是有没有思考过为什么要把数据类型这样分呢?本质上,是因为基本数据类型保存在栈内存,而引用类型保存在堆内存中。那再进一步问:为什么要分两种保存方式呢? 根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在占内存中以供我们访问。举个例子:

var a = 1;//定义了一个number类型
var obj1 = {//定义了一个objr类型
    name:'obj'
};

在执行这段代码后,内存空间里是这样的:


栈内存和堆内存
因为这种保存方式的存在,所以我们在操作变量的时候,如果是基本数据类型,则按值访问,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址类操作实际对象。从而也引出了所谓的深浅复制问题。

深复制和浅复制

不同的复制方式

紧接着上文的内容,假设有以下代码:

//例子1
var a = 1;
var b = a;//复制
console.log(b)//1
a = 2;//改变a的值
console.log(b)//1

可以看到,我们复制完b以后,即使改变a的值,b也不会改变,因为a和b是相互独立的,按照上面的图,也就是在栈内存中创建了一个变量b 保存的值也是2;

//例子2
var color1 = ['red','green'];
var color2 = color1;//复制
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green','black']

在例子2中,我们按照完全相同的步骤,操作了一个数组,但是返回的结果却完全不一样,因为此时的复制,实际上是这样:
数组的复制
我们只是复制了一次引用类型的地址而已,所以,不管接下来我们是操作color1还是color2,本质上都是操作同一个数组对象

深复制和浅复制

刚刚说到,简单的赋值没有办法复制引用类型,那如果我们就是想复制上面的color1数组怎么办呢?可以这样:

var color1 = ['red','green']; 
var color2 = [];
//复制
for(var i  = 0;i < color1.length;i++){
    color2[i] = color1[i]; 
}
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green']

这一次我们先创建了一个空数组color2,然后让color2的每个值都和color1对应相等,最后的color1color2是相互独立的了,满足了我们的需要。当然对于对象类型也是一样的,使用for-in遍历取代这里的for循环即可。

问题真的就这样解决了吗?当然没有,不过以上这种只复制了第一层属性的方式就叫做浅复制,浅复制有什么缺陷呢?我们可以先思考一下,从直接使用=符号赋值进行复制到浅复制,能够复制成功(成功是指复制的结果与复制源完全独立),是因为我们复制的对象都是基本类型,怎么解释呢?

  • 在复制基本数据类型时,我们直接使用=完成复制
  • 在引用类型的时候,我们循环遍历对象,对每个属性或值使用=完成复制

有没有注意到上文的color1例子使用浅复制之所以能够复制成功,是因为数组中的每一项都是基本数据类型(string),所以猜出了浅复制的局限了吗?假如数组中某一项保存的是一个对象,或者是一个数组,又或者对象的某个属性还是一个对象呢?(换句话说就是引用类型的某个属性还是引用类型),如:

var person = {
    name:'lin',
    score:{
        physics:85,
        math:99
    }
}

这个对象的分数score属性就还是一个对象,那我们使用前面提到for-in遍历复制的时候,对score的复制,不就又变成了我们前面提到的只复制了地址的情况吗?再想想浅复制实现的原理,相信大家猜到了深复制实现的方式:对属性中所有引用类型的值,遍历到是基本类型的值为止,从这种方式上,我们很容易就可以想到利用递归来实现深复制。

function deepCopy (obj) {
    var result;

    //引用类型分数组和对象分别递归
    if (Object.prototype.toString.call(obj) == '[object Array]') {
      result = []
      for (i = 0; i < obj.length; i++) {
        result[i] = deepCopy(obj[i])
      }
    } else if (Object.prototype.toString.call(obj) == '[object Object]') {
      result = {}
      for (var attr in obj) {
        result[attr] = deepCopy(obj[attr])
      }
    }
    //值类型直接返回
    else {
      return obj
    }
    return result
}

上面的函数很简单:对于传入的参数,首先判断是否为引用类型,如果不是,直接返回即可;如果是,循环遍历该对象的属性,如果某个属性还是引用类型,则针对该属性再次调用deepCopy函数,从而完成深复制。

附注

对于浅复制,其实还有其他的实现方式,比如数组中concatslice方法,对于这些还是希望大家自己了解,本本主要针对深浅复制的实现原理进行解析。

小结

对于深浅复制的区别,其实核心的关键点就是是只复制了第一属性还是完全复制了所有的属性,可能有些地方写的稍显啰嗦或者描述不当,欢迎提出意见和建议(然而我并不一定会听,哈哈)。如果对读者有帮助,还是希望能点个推荐。以上内容属于个人见解,如果有不同意见,欢迎指出和探讨。请尊重作者的版权,转载请注明出处,如作商用,请与作者联系,感谢!

(1)普通用户端(全平台) 音乐播放核心体验: 个性化首页:基于 “听歌历史 + 收藏偏好” 展示 “推荐歌单(每日 30 首)、新歌速递、相似曲风推荐”,支持按 “场景(通勤 / 学习 / 运动)” 切换推荐维度。 播放页功能:支持 “无损音质切换、倍速播放(0.5x-2.0x)、定时关闭、歌词逐句滚动”,提供 “沉浸式全屏模式”(隐藏冗余控件,突出歌词与专辑封面)。 多端同步:自动同步 “播放进度、收藏列表、歌单” 至所有登录设备(如手机暂停后,电脑端打开可继续播放)。 音乐发现与管理: 智能搜索:支持 “歌曲名 / 歌手 / 歌词片段” 搜索,提供 “模糊匹配(如输入‘晴天’联想‘周杰伦 - 晴天’)、热门搜索词推荐”,结果按 “热度 / 匹配度” 排序。 歌单管理:创建 “公开 / 私有 / 加密” 歌单,支持 “批量添加歌曲、拖拽排序、一键分享到社交平台”,系统自动生成 “歌单封面(基于歌曲风格配色)”。 音乐分类浏览:按 “曲风(流行 / 摇滚 / 古典)、语言(国语 / 英语 / 日语)、年代(80 后经典 / 2023 新歌)” 分层浏览,每个分类页展示 “TOP50 榜单”。 社交互动功能: 动态广场:查看 “关注的用户 / 音乐人发布的动态(如‘分享新歌感受’)、好友正在听的歌曲”,支持 “点赞 / 评论 / 转发”,可直接点击动态中的歌曲播放。 听歌排行:个人页展示 “本周听歌 TOP10、累计听歌时长”,平台定期生成 “全球 / 好友榜”(如 “好友中你本周听歌时长排名第 3”)。 音乐圈:加入 “特定曲风圈子(如‘古典音乐爱好者’)”,参与 “话题讨论(如‘你心中最经典的钢琴曲’)、线上歌单共创”。 (2)音乐人端(创作者中心) 作品管理: 音乐上传:支持 “无损音频(FLAC/WAV)+ 歌词文件(LRC)+ 专辑封面” 上传,填写 “歌曲信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值