JS中实现浅拷贝和深拷贝的代码详解


Posted in Javascript onJune 05, 2019

(一)JS中基本类型和引用类型

JavaScript的变量中包含两种类型的值:基本类型值 和 引用类型值,在内存中的表现形式在于:前者是存储在栈中的一些简单的数据段,后者则是保存在堆内存中的一个对象。

基本类型值

在JavaScript中基本数据类型有 String , Number , Undefined , Null , Boolean ,在ES6中,又定义了一种新的基本数据类型 Symbol ,所以一共有6种。

基本类型是按值访问的,从一个变量复制基本类型的值到另一个变量后,这两个变量的值是完全独立的,即使一个变量改变了也不会影响到第二个变量。

var str1 = '撩课';
var str2 = str1;
str2 = 'itlike';
console.log(str2); //'itlike'
console.log(str1); //'撩课'

引用类型值

引用类型值是引用类型的实例,它是保存在堆内存中的一个对象,引用类型是一种数据结构,最常用的是Object,Array,Function类型,此外还有Date,RegExp,Error等。

在ES6中提供了Set,Map2种新的数据结构。

(二)JS中如何复制引用类型的

基本类型和引用类型赋值的差异化
举个例子:在下面代码中,只修改了obj1中的name属性,却同时改变了ob1和obj2中的name属性。

var obj1 = {'name': '撩课'};
var obj2 = obj1;
obj2.name = '小撩';
console.log(obj1); // {'name': '撩课'}
console.log(obj2); // {'name': '撩课'}

当变量复制引用类型值的时候,同样和基本类型值一样会将变量的值复制到新变量上,不同的是对于变量的值,它是一个指针,指向存储在堆内存中的对象。

因为,在JS中,堆内存中的对象无法直接访问,必须要访问这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值。

(三)浅拷贝

在JS中,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址;所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

下面是JavaScript提供的浅拷贝方法:

Object.assign

ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标,剩下的参数是拷贝的源对象;

语法:Object.assign(target, ...sources)

var p = {
  'name': '张三',
};
var copyP = {};
Object.assign(copyP, p);
console.log(copyP);
console.log(p);

Object.assign是一个浅拷贝,它只是在根属性(对象的第一层级)创建了一个新的对象,但是如果属性的值是对象的话,只会拷贝一份相同的内存地址。

扩展运算符

利用扩展运算符可以在构造字面量对象时,进行克隆或者属性拷贝。语法如下:

var cloneObj = { ...obj };
var obj = {'name': '撩课', 'college': ['H5','JAVA','Python']}
var obj2 = {...obj};
obj.name='小撩';
//{'name': '小撩', 'college': ['H5','JAVA','Python']}
console.log(obj);
//{'name': '撩课', 'college': ['H5','JAVA','Python']}
console.log(obj2); 
obj.college.push('Go');
//{'name': '小撩', 'college': ['H5','JAVA','Python','Go']}
console.log(obj); 
//{'name': '小撩', 'college': ['H5','JAVA','Python','Go']}
console.log(obj2);

扩展运算符和Object.assign()存在同样的问题,对于值是对象的属性无法完全拷贝成两个不同对象;

但是如果属性都是基本类型的值的话,使用扩展运算符更加简洁。

(四)深拷贝

浅拷贝只在根属性上在堆内存中创建了一个新的的对象,复制了基本类型的值,但是复杂数据类型也就是对象则是拷贝相同的地址。

而深拷贝则是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

JSON.stringify

JSON.stringify()是目前开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在内存中,再用JSON.parse()反序列化将JSON字符串变成一个新的对象。

举个例子:

var obj = {
  name: '撩课',
  age: 18,
  friends: ['小花', '小黑'],
  goodF: {
    name: '小撩',
    age: 19,
    address: '上海',
    pets: [{name: '土豆'}, {name: '马铃薯'}]},
  bir: new Date()
};

var newObj = JSON.parse(JSON.stringify(obj));
obj.goodF.pets[0].name = '旺财';
console.log(newObj);
console.log(obj);

使用JSON.stringify实现深拷贝有几点要注意:

1)拷贝的对象的值中如果有函数,undefined,symbol,经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失;

无法拷贝不可枚举的属性,无法拷贝对象的原型链
3)拷贝Date引用类型会变成字符串

4)拷贝RegExp引用类型会变成空对象

对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null

递归实现深拷贝

具体实现如下:

/**
 * 辅助函数, 判定是否是对象
 * @param obj
 * @returns {boolean}
 */
function isObj(obj) {
  return obj instanceof Object;
}

/**
 * 深拷贝fromObj面的所有属性/值, 到toObj对象里面
 * @param fromObj 拷贝对象
 * @param toObj  目标对象
 */
function deepCopyObj2NewObj(fromObj, toObj) {
  for (var key in fromObj) {
    if(fromObj.hasOwnProperty(key)){
      var fromValue = fromObj[key];
      // 如果是值类型,那么就直接拷贝赋值
      if (!isObj(fromValue)) {
        toObj[key] = fromValue;
      } else {
        // 如果是引用类型,那么就再调用一次这个方法,
        // 去内部拷贝这个对象的所有属性
        // fromValue是什么类型, 创建一个该类型的空对象
        var tmpObj = new fromValue.constructor;

        // console.log(tmpObj);
        // debugger;
        deepCopyObj2NewObj(fromValue, tmpObj);
        toObj[key] = tmpObj;
      }
    }
  }
}

(五)总结

1)在日常开发中一般并不需要拷贝很多特殊的引用类型,深拷贝对象使用JSON.stringify是最直接和简单的方法。

2)实现一个完整的深拷贝是非常复杂的,需要考虑到很多边界情况。对于特殊的引用类型有拷贝需求的话,建议借助第三方完整的库。

以上所述是小编给大家介绍的JS中实现浅拷贝和深拷贝的代码详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
javascript 四则运算精度修正函数代码
May 31 Javascript
jquery 触发a链接点击事件解决方案
May 02 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
Nov 11 Javascript
jquery正则表达式验证(手机号、身份证号、中文名称)
Dec 31 Javascript
Vue数据驱动模拟实现1
Jan 11 Javascript
vue2笔记 — vue-router路由懒加载的实现
Mar 03 Javascript
vuejs事件中心管理组件间的通信详解
Aug 09 Javascript
浅谈vue方法内的方法使用this的问题
Sep 15 Javascript
详解Vue项目中实现锚点定位
Apr 24 Javascript
原生js通过一行代码实现简易轮播图
Jun 05 Javascript
jquery 插件重新绑定的处理方法分析
Nov 23 jQuery
解决小程序无法触发SESSION问题
Feb 03 Javascript
优雅的处理vue项目异常实战记录
Jun 05 #Javascript
原生JS使用Canvas实现拖拽式绘图功能
Jun 05 #Javascript
Node.js 路由的实现方法
Jun 05 #Javascript
JS实现动态添加外部js、css到head标签的方法
Jun 05 #Javascript
JS函数动态传递参数的方法分析【基于arguments对象】
Jun 05 #Javascript
jQuery操作cookie的示例代码
Jun 05 #jQuery
JS实现从对象获取对象中单个键值的方法示例
Jun 05 #Javascript
You might like
PHP出错界面
2006/10/09 PHP
UCenter 批量添加用户的php代码
2012/07/17 PHP
PHP下获取上个月、下个月、本月的日期(strtotime,date)
2014/02/02 PHP
一种JavaScript的设计模式
2006/11/22 Javascript
Jquery 组合form元素为json格式,asp.net反序列化
2009/07/09 Javascript
基于jquery的blockui插件显示弹出层
2011/04/14 Javascript
JS实现鼠标点击展开或隐藏表格行的方法
2015/03/03 Javascript
有关Promises异步问题详解
2015/11/13 Javascript
jQuery基本选择器(实例及表单域value的获取方法)
2016/05/20 Javascript
JS弹出窗口插件zDialog简单用法示例
2016/06/12 Javascript
jquery实现网站列表切换效果的2种方法
2016/08/12 Javascript
JS动态给对象添加属性和值的实现方法
2016/10/21 Javascript
微信小程序 picker 组件详解及简单实例
2017/01/10 Javascript
jquery mobile移动端幻灯片滑动切换效果
2020/04/15 Javascript
微信扫码支付零云插件版实例详解
2017/04/26 Javascript
自制简易打赏功能的实例
2017/09/02 Javascript
Vue 中的受控与非受控组件的实现
2018/12/17 Javascript
详解vue-router导航守卫
2019/01/19 Javascript
vue父组件触发事件改变子组件的值的方法实例详解
2019/05/07 Javascript
layui导出所有数据的例子
2019/09/10 Javascript
vue.js click点击事件获取当前元素对象的操作
2020/08/07 Javascript
[05:04]完美世界携手游戏风云打造 卡尔工作室地图界面篇
2013/04/23 DOTA
Python编程中归并排序算法的实现步骤详解
2016/05/04 Python
详解Python中open()函数指定文件打开方式的用法
2016/06/04 Python
django传值给模板, 再用JS接收并进行操作的实例
2018/05/28 Python
python遍历文件夹找出文件夹后缀为py的文件方法
2018/10/21 Python
Python closure闭包解释及其注意点详解
2019/08/28 Python
Python实现搜索算法的实例代码
2020/01/02 Python
python中return不返回值的问题解析
2020/07/22 Python
python分布式爬虫中消息队列知识点详解
2020/11/26 Python
解决pycharm修改代码后第一次运行不生效的问题
2021/02/06 Python
Pytorch 中的optimizer使用说明
2021/03/03 Python
加热夹克:RAVEAN
2018/10/19 全球购物
计算机大学生的自我评价
2013/10/15 职场文书
事业单位接收函
2014/01/10 职场文书
新手入门Mysql--sql执行过程
2021/06/20 MySQL