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 相关文章推荐
关于二级域名下使用一级域名下的COOKIE的问题
Nov 07 Javascript
鼠标经过tr时,改变tr当前背景颜色
Jan 13 Javascript
js实现类似于add(1)(2)(3)调用方式的方法
Mar 04 Javascript
jquery使用slideDown实现模块缓慢拉出效果的方法
Mar 27 Javascript
jQuery实现将div中滚动条滚动到指定位置的方法
Aug 10 Javascript
Vue.js实现无限加载与分页功能开发
Nov 03 Javascript
基于zepto.js实现手机相册功能
Jul 11 Javascript
通过源码分析Vue的双向数据绑定详解
Sep 24 Javascript
js 开发之autocomplete="off"在chrom中失效的解决办法
Sep 28 Javascript
Angular4编程之表单响应功能示例
Dec 13 Javascript
vue cli3.0结合echarts3.0与地图的使用方法示例
Mar 26 Javascript
vue 通过绑定事件获取当前行的id操作
Jul 27 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中cookie实现二级域名可访问操作的方法
2014/11/11 PHP
thinkphp备份数据库的方法分享
2015/01/04 PHP
php中文验证码实现方法
2015/06/18 PHP
php 一维数组的循环遍历实现代码
2017/04/10 PHP
php封装的pdo数据库操作工具类与用法示例
2019/05/08 PHP
Thinkphp5.0框架视图view的循环标签用法示例
2019/10/12 PHP
JavaScript是否可实现多线程  深入理解JavaScript定时机制
2009/12/22 Javascript
jquery scrollTop方法根据滚动像素显示隐藏顶部导航条
2013/05/27 Javascript
解析javascript 数组以及json元素的添加删除
2013/06/26 Javascript
js加入收藏以及使用Jquery更改透明度
2014/01/26 Javascript
js改变鼠标的形状和样式的方法
2014/03/31 Javascript
我的Node.js学习之路(二)NPM模块管理
2014/07/06 Javascript
JS实现超精简的链接列表在固定区域内滚动效果代码
2015/11/04 Javascript
一个字符串中出现次数最多的字符 统计这个次数【实现代码】
2016/04/29 Javascript
js实现hashtable的赋值、取值、遍历操作实例详解
2016/12/25 Javascript
Bootstrap多级菜单的实现代码
2017/05/23 Javascript
Vue的编码技巧与规范使用详解
2019/08/28 Javascript
微信小程序获取位置展示地图并标注信息的实例代码
2019/09/01 Javascript
详解Nuxt内导航栏的两种实现方式
2020/04/16 Javascript
Python中的True,False条件判断实例分析
2015/01/12 Python
Python Socket传输文件示例
2017/01/16 Python
python3+PyQt5实现自定义窗口部件Counters
2018/04/20 Python
python 将list转成字符串,中间用符号分隔的方法
2018/10/23 Python
对python内置map和six.moves.map的区别详解
2018/12/19 Python
彻底搞懂 python 中文乱码问题(深入分析)
2020/02/28 Python
pytorch读取图像数据转成opencv格式实例
2020/06/02 Python
python 装饰器重要在哪
2021/02/14 Python
英国日常交易网站:Wowcher
2018/09/04 全球购物
Ibatis中如何提高SQL Map的性能
2013/05/11 面试题
酒店实习个人鉴定
2013/12/07 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
幼儿园托班开学寄语(2015秋季)
2015/05/27 职场文书
宇宙与人观后感
2015/06/05 职场文书
浅谈golang package中init方法的多处定义及运行顺序问题
2021/05/06 Golang
SpringBoot+VUE实现数据表格的实战
2021/08/02 Java/Android
MySQL数据管理操作示例讲解
2022/12/24 MySQL