详解JS深拷贝与浅拷贝


Posted in Javascript onAugust 04, 2020

一、预备知识

1.1、JS数据类型

基本数据类型:Boolean、String、Number、null、undefined
引用数据类型:Object、Array、Function、RegExp、Date等

1.2、数据类型的复制

基本数据类型的复制,是按值传递的

var a = 1;
var b = a;
b = 2;
console.log(a); // 1
console.lob(b); // 2

引用数据类型的复制,是按引用传值

var obj1 = {
 a: 1;
 b: 2;
};
var obj2 = obj1;
obj2.a=3;
console.log(obj1.a); //3
console.log(obj2.a); // 3

1.3、深拷贝与浅拷贝

深拷贝和浅拷贝都只针对引用数据类型,浅拷贝会对对象逐个成员依次拷贝,但只复制内存地址,而不复制对象本身,新旧对象成员还是共享同一内存;深拷贝会另外创建一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

区别:浅拷贝只复制对象的第一层属性,而深拷贝会对对象的属性进行递归复制。

二、JS浅拷贝

2.1、赋值与浅拷贝

当把一个对象赋值给一个新的变量时,赋的对象是该对象在栈中的地址,而不是堆中的数据。也就是新旧两个对象指的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,两个对象联动的会一起改变。

var obj1 = {
  'name' : 'zhangsan',
  'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

详解JS深拷贝与浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,对原有对象的成员进行依次拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。因此如果新对象中的某个对象成员改变了地址,就会影响到原有的对象。

//手写浅拷贝
function shallowCopy(obj1) {
 let obj2 = Array.isArray(obj1) ? [] : {}
 for (let i in obj1) {
  obj2[i] = obj1[i]
 }
 return obj2
}
var obj1 = {
  'name' : 'zhangsan',
  'language' : [1,[2,3],[4,5]],
};
var obj2 = shallowCopy(obj1);
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

详解JS深拷贝与浅拷贝

2.2、浅拷贝的实现

(1)Object.assign()

Object.assign()方法可以把源对象自身的任意多个的可枚举属性拷贝给目标对象,然后返回目标对象,但是Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。此方法对于Array和Object均可适用。

var obj1 = {
  'name' : 'zhangsan',
  'language' : [1,[2,3],[4,5]],
};
var obj2 = Object.assign({}, obj1);
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

详解JS深拷贝与浅拷贝

(2)Array.prototype.concat()和Array.prototype.slice()

Array.prototype.concat()和Array.prototype.slice()均为Array原型上的方法,只适用于Array。

var arr1 = [1,3,{
 user: 'aaa'
}]
var arr2 = arr1.concat();
arr2[0] = '一';
arr2[2].user = 'AAA';
console.log('arr1',arr1)
console.log('arr2',arr2)


var arr1 = [1,3,{
 user: 'aaa'
}]
var arr2 = arr1.slice();
arr2[0] = '一';
arr2[2].user = 'AAA';
console.log('arr1',arr1)
console.log('arr2',arr2)

详解JS深拷贝与浅拷贝

补充说明:Array的slice和contact方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。这两种方法同Object.assign()一样,都是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。

三、JS深拷贝

对对象的属性中所有引用类型的值,遍历到是基本类型的值为止。

3.1、深拷贝实现方式

(1)JSON.parse(JSON.stringify())

原理:用JSON.stringify()将对象转成字符串,再用JSON.parse()把字符串解析成对象。

var obj1 = {
  'name' : 'zhangsan',
  'language' : [1,[2,3],[4,5]],
};
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

详解JS深拷贝与浅拷贝

缺点:这种方法可以实现数组和对象和基本数据类型的深拷贝,但不能处理函数。因为JSON.stringify()方法是将一个javascript值转换我一个JSON字符串,不能接受函数。其他影响如下:

  • 如果对象中有时间对象,那么用该方法拷贝之后的对象中,时间是字符串形式而不是时间对象
  • 如果对象中有RegExp、Error对象,那么序列化的结果是空
  • 如果对象中有函数或者undefined,那么序列化的结果会把函数或undefined丢失
  • 如果对象中有NAN、infinity、-infinity,那么序列化的结果会变成null
  • JSON.stringfy()只能序列化对象的可枚举自有属性,如果对象中有是构造函数生成的,那么拷贝后会丢弃对象的constructor
  • 如果对象中存在循环引用也无法正确实现深拷贝

(2)手写深拷贝函数

通过递归实现深拷贝

function deepCopy(obj){
 var result= Array.isArray(obj) ? [] : {}
 if (obj && typeof(obj) === 'object') {
  for (let i in obj) {
   if (obj.hasOwnProperty(i)){ // 思考:这句是否有必要?
    if (obj[i] && typeof(obj[i]) === 'object') {
     result[i] = deepCopy(obj[i])
    } else {
     result[i] = obj[i]
    }
   }
  }
 }
 return result
}
var obj1 = {
 a: 1,
 b: {
  c: 2
 }
};
var obj2 = deepCopy(obj1);
obj2.a = '一';
obj2.b.c = '二'
console.log('obj1', obj1)
console.log('obj2', obj2)

obj.hasOwnProperty(prop)用来判断obj这个对象中是否含有prop这个属性,返回布尔值,有则true,没有则false

以上有个缺陷:当遇到两个互相引用的对象时,会出现死循环的情况,从而导致爆栈。为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否互相引用。

深拷贝函数改进(防止循环递归爆栈)

function deepCopy(obj, parent = null) {
 let result = Array.isArray(obj) ? [] : {}
 let _parent = parent
 // 该字段有父级则需要追溯该字段的父级
 while(_parent) {
  // 如果该字段引用了它的父级,则为循环引用
  if (_parent.originalParent === obj) {
   // 循环引用返回同级的新对象
   return _parent.currentParent 
  }
  _parent = _parent.parent
 }
 if (obj && typeof(obj) === 'object') {
  for (let i in obj) {
   // 如果字段的值也是一个对象
   if (obj[i] && typeof(obj[i]) === 'object') {
    // 递归执行深拷,将同级的待拷贝对象传递给parent,方便追溯循环引用
    result[i] = deepCopy(obj[i], {
     originalParent: obj,
     currentParent: result,
     parent: parent
    })
   } else {
    result[i] = obj[i]
   }
  }
 }
 return result
}
var obj1 = {
 x: 1,
 y: 2
};
obj1.z = obj1
var obj2 = deepCopy(obj1)
console.log('obj1', obj1)
console.log('obj2', obj2)

以上代码可以复制到浏览器去试试吧

深拷贝函数最终版(支持基本数据类型、Array、Object、原型链、RegExp、Date类型)

function deepCopy(obj, parent = null) {
 let result
 let _parent = parent
 while(_parent) {
  if (_parent.originalParent === obj) {
   return _parent.currentParent
  }
  _parent = _parent.parent
 }
 if (obj && typeof(obj) === 'object') {
  if (obj instanceof RegExp) {
   result = new RegExp(obj.source, obj.flags)
  } else if (obj instanceof Date) {
   result = new Date(obj.getTime())
  } else {
   if (obj instanceof Array) {
    result = []
   } else {
    let proto = Object.getPrototypeOf(obj)
    result = Object.create(proto)
   }
   for (let i in obj) {
    if(obj[i] && typeof(obj[i]) === 'object') {
     result[i] = deepCopy(obj[i], {
      originalParent: obj,
      currentParent: result,
      parent: parent
     })
    } else {
     result[i] = obj[i]
    }
   }
  }
 } else {
  return obj
 }
 return result
}
var obj1 = {
 x: 1 
}

//试调用
function construct(){
  this.a = 1,
  this.b = {
    x:2,
    y:3,
    z:[4,5,[6]]
  },
  this.c = [7,8,[9,10]],
  this.d = new Date(),
  this.e = /abc/ig,
  this.f = function(a,b){
    return a+b
  },
  this.g = null,
  this.h = undefined,
  this.i = "hello",
  this.j = Symbol("foo")
}
construct.prototype.str = "I'm prototype"
var obj1 = new construct()
obj1.k = obj1
obj2 = deepCopy(obj1)

obj2.b.x = 999
obj2.c[0] = 666

console.log('obj1', obj1)
console.log('obj2', obj2)

(3)函数库

也可以使用一些函数库,比如函数库lodash,也有提供_.cloneDeep用来做深拷贝;

var _ = require('lodash');
var obj1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

参考

以上就是详解JS深拷贝与浅拷贝的详细内容,更多关于JS深拷贝与浅拷贝的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
Jquery Ajax学习实例3 向WebService发出请求,调用方法返回数据
Mar 16 Javascript
div+css布局的图片连续滚动js实现代码
May 04 Javascript
JavaScript初学者应注意的七个细节详细介绍
Dec 27 Javascript
JS实现从网页顶部掉下弹出层效果的方法
Aug 06 Javascript
移动端js图片查看器
Nov 17 Javascript
原生JS实现图片懒加载(lazyload)实例
Jun 13 Javascript
JavaScript注册时密码强度校验代码
Jun 30 Javascript
node简单实现一个更改头像功能的示例
Dec 29 Javascript
vue init webpack 建vue项目报错的解决方法
Sep 29 Javascript
JavaScript实现英语单词题库
Dec 24 Javascript
Element Dialog对话框的使用示例
Jul 26 Javascript
vue报错function () { [native code] },无法出现我们想要的内容 Unknown custom element
Apr 11 Vue.js
vue addRoutes路由动态加载操作
Aug 04 #Javascript
vue+element使用动态加载路由方式实现三级菜单页面显示的操作
Aug 04 #Javascript
Vue登录拦截 登录后继续跳转指定页面的操作
Aug 04 #Javascript
vue 解决uglifyjs-webpack-plugin打包出现报错的问题
Aug 04 #Javascript
浅谈在vue-cli3项目中解决动态引入图片img404的问题
Aug 04 #Javascript
解决vuecli3中img src 的引入问题
Aug 04 #Javascript
简介JavaScript错误处理机制
Aug 04 #Javascript
You might like
php UTF8 文件的签名问题
2009/10/30 PHP
基于php split()函数的用法详解
2013/06/05 PHP
PHP5全版本绕过open_basedir读文件脚本漏洞详细介绍
2015/01/20 PHP
浅谈php和js中json的编码和解码
2016/10/24 PHP
使用正则去除php代码中的注释方法
2016/11/03 PHP
php写入文件不覆盖的实例讲解
2019/09/17 PHP
TP5框架model常见操作示例小结【增删改查、聚合、时间戳、软删除等】
2020/04/05 PHP
Yii框架多语言站点配置方法分析【中文/英文切换站点】
2020/04/07 PHP
打开超链需要“确认”对话框的方法
2007/03/08 Javascript
javascript 寻找错误方法整理
2014/06/15 Javascript
在css加载完毕后自动判断页面是否加入css或js文件
2014/09/10 Javascript
动态加载js的方法汇总
2015/02/13 Javascript
Bootstrap的图片轮播示例代码
2015/08/31 Javascript
修复jQuery tablesorter无法正确排序的bug(加千分位数字后)
2016/03/30 Javascript
JavaScript暂停和继续定时器的实现方法
2016/07/18 Javascript
jQuery插件HighCharts绘制2D柱状图、折线图和饼图的组合图效果示例【附demo源码下载】
2017/03/09 Javascript
javascript 中事件冒泡和事件捕获机制的详解
2017/09/01 Javascript
浅谈Vue-cli 命令行工具分析
2017/11/22 Javascript
详解操作虚拟dom模拟react视图渲染
2018/07/25 Javascript
layui table表格数据的新增,修改,删除,查询,双击获取行数据方式
2019/11/14 Javascript
[52:12]FNATIC vs Infamous 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
跟老齐学Python之坑爹的字符编码
2014/09/28 Python
Python读写/追加excel文件Demo分享
2018/05/03 Python
关于Python 常用获取元素 Driver 总结
2019/11/24 Python
tensorflow实现训练变量checkpoint的保存与读取
2020/02/10 Python
Python 如何查找特定类型文件
2020/08/17 Python
解决virtualenv -p python3 venv报错的问题
2021/02/05 Python
巴西宠物商店:Cobasi
2019/04/19 全球购物
彪马荷兰官网:PUMA荷兰
2019/05/08 全球购物
促销活动方案模板
2014/02/24 职场文书
会计个人实习计划书
2014/08/15 职场文书
2016年高校自主招生自荐信范文
2015/03/24 职场文书
PyQt5 QThread倒计时功能的实现代码
2021/04/02 Python
Go Gin实现文件上传下载的示例代码
2021/04/02 Golang
Python机器学习之PCA降维算法详解
2021/05/19 Python
「回转企鹅罐」10周年纪念展「輪るピングドラム展」海报公开
2022/03/22 日漫