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 事件记录使用说明
Oct 20 Javascript
jQuery下的几个你可能没用过的功能
Aug 29 Javascript
js控制网页前进和后退的方法
Jun 08 Javascript
JavaScript中的Promise使用详解
Jun 24 Javascript
继续学习javascript闭包
Dec 03 Javascript
js实现用户输入的小写字母自动转大写字母的方法
Jan 21 Javascript
AngularJS报错$apply already in progress的解决方法分析
Jan 30 Javascript
详解Layer弹出层样式
Aug 21 Javascript
layui 点击重置按钮, select 并没有被重置的解决方法
Sep 03 Javascript
jquery实现直播视频弹幕效果
Feb 25 jQuery
jQuery 添加元素和删除元素的方法
Jul 15 jQuery
Vue(定时器)解决mounted不能获取到data中的数据问题
Jul 30 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
浅析Yii中使用RBAC的完全指南(用户角色权限控制)
2013/06/20 PHP
解析Win7 XAMPP apache无法启动的问题
2013/06/26 PHP
PHP的mysqli_query参数MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的区别
2014/09/29 PHP
php基于PDO实现功能强大的MYSQL封装类实例
2017/02/27 PHP
PHP判断是手机端还是PC端 PHP判断是否是微信浏览器
2017/03/15 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
php插入mysql数据返回id的方法
2018/05/31 PHP
PHP实现简易用户登录系统
2020/07/10 PHP
Knockoutjs快速入门(经典)
2012/12/24 Javascript
jquery validation验证身份证号,护照,电话号码,email(实例代码)
2013/11/06 Javascript
jQuery修改class属性和CSS样式整理
2015/01/30 Javascript
图解prototype、proto和constructor的三角关系
2016/07/31 Javascript
JavaScript实现经典排序算法之冒泡排序
2016/12/28 Javascript
基于Vue 服务端Cookies删除的问题
2018/09/21 Javascript
vue表单验证你真的会了吗?vue表单验证(form)validate
2019/04/07 Javascript
vue项目前端知识点整理【收藏】
2019/05/13 Javascript
详解微信小程序支付流程与梳理
2019/07/16 Javascript
vue学习笔记之Vue中css动画原理简单示例
2020/02/29 Javascript
python Django批量导入不重复数据
2016/03/25 Python
Python实现的计算器功能示例
2018/04/26 Python
Matplotlib 生成不同大小的subplots实例
2018/05/25 Python
Python迭代器与生成器用法实例分析
2018/07/09 Python
自定义django admin model表单提交的例子
2019/08/23 Python
Python常用模块sys,os,time,random功能与用法实例分析
2020/01/07 Python
Python集成开发工具Pycharm的安装和使用详解
2020/03/18 Python
用 python 进行微信好友信息分析
2020/11/28 Python
韩国家庭购物网上商店:Nsmall
2017/05/07 全球购物
车库门开启器、遥控器和零件:Chamberlain
2019/04/09 全球购物
美国领先的个性化礼品商城:Personalization Mall
2019/07/27 全球购物
幼儿园保育员辞职信
2014/01/12 职场文书
优秀学生干部推荐材料
2014/02/03 职场文书
《在山的那边》教学反思
2014/02/23 职场文书
消防安全宣传口号
2014/06/10 职场文书
社区文明创建工作总结2015
2015/04/21 职场文书
HTML基础详解(上)
2021/10/16 HTML / CSS