对象深拷贝和浅拷贝的区别:
浅拷贝: 赋值对象的引用,而不是对象的本身;
深拷贝: 把复制的对象所引用的全部对象都复制一遍;
- 解决方案:
关于复杂数据类型的拷贝问题,在实际工作过程中,用到的还是比较多的,之前有试过Object.assign()和JSON.parse( JSON.stringify(obj))等方法,发现其实这两种方法都有弊端,所以今天特意总结和大家分享一个封装的方法,可以解决复杂数据的深拷贝问题,算是比较完善的解决方案,如果任何改善的建议,欢迎留言;
// 通过对传入数据类型的判断及for in 实现比较完善的深拷贝方法
function deepCopy (obj) {
// 简单数据类型或者null直接return obj
if (obj == null || typeof (obj) != 'object') {
return obj
}
var type = obj.constructor;
// Date或RegExp也直接return对应值
if(type == Date) { return new Date(obj) }
if(type == RegExp) { return new RegExp(obj) }
var temp = obj.constructor()
for (var key in obj) {
// 不遍历其原型链上的属性
if (obj.hasOwnProperty(key)) {
// 注意这里的递归调用
temp[key] = deepCopy(obj[key]);
}
}
return temp;
}
// 示例 :
var obj = {
name:'wangfei',
fun:function(){
console.log('fun')
},
arr:[1,2,{location:{
nation:['china','japan']
}}],
data:undefined,
}
// 调用方法对数据进行拷贝;
var copyObj = deepCopy(obj);
// 修改拷贝后的值;
copyObj .arr[0] = '啊哈哈';
copyObj .arr[2].location = 'helloworld';
// 打印输出结果,进行对比;
console.log(JSON.stringify(obj))
// {"name":"wangfei","arr":[1,2,{"location":{"nation":["china","japan"]}}]}
console.log(JSON.stringify(copyObj))
// {"name":"wangfei","arr":["啊哈哈",2,{"location":"helloworld"}]}
小结: 通过以上代码发现,封装的函数可以实现对复杂数据类型的深度拷贝,是一个比较完善的方案;
如果是比较着急解决这个问题的朋友,可以先拷贝上面的函数去用,然后再看下面的部分;
- 基本数据的赋值问题
// 基本数据类型(Number,String,Boolean,Undefined,Null)的拷贝可以直接使用“=”;
// Number 类型
var a = 1;
var b = a;
b = 2;
console.log(a,b); // 输出 1 2;
// String类型;
var a = 'hellowold';
var b = a;
b = 'demo';
console.log(a,b); // 输出 hellowold demo
//Boolean类型
var a = false;
var b = a;
b = 'hello';
console.log(a,b); // 输出 false "hello"
// Undefined;
var a = undefined;
var b = a;
b = false;
console.log(a,b); //输出 undefined false
// Null
var a = null;
var b = a;
b = undefined;
console.log(a,b); // 输出null undefined;
通过上面的代码,我们可以发现 基本数据类型可以直接通过“=” 进行赋值,两者之间不会受到影响;
- 复杂数据类型
下面会以对象,数组,函数,日期对象以及正则等为例进行分析
1 对象数据类型
let obj = {name : 'zhangsan'};
// 用 “=” 进行赋值
let copyObj = obj;
copyObj.name = 'lisi';
// 输出结果进行对比
console.log(obj) // {name: "lisi"}
console.log(copyObj) // {name: "lisi"}
2 数组
let arr = ['a',1,'b'];
// 用 “=” 进行赋值
let copyArr = arr;
copyArr[0] = 'first';
// 输出结果进行对比
console.log(arr) // 输出 ["first", 1, "b"]
console.log(copyArr) // 输出 ["first", 1, "b"]
3 函数
let fun1 = function() {
alert('fun1');
}
// 用 “=” 进行赋值
let fun2 = fun1;
fun2 = function() {
alert('fun2')
}
// 输出结果进行对比
console.log(fun1.toString())
// function () {alert('fun1'); }
console.log(fun2)
// function () {alert('fun2'); }
4 日期(Date)
var time = new Date('2019-07-12 00:00:00');
// 用 “=” 进行赋值
var copyTime = time;
copyTime = new Date('2012-12-12: 00:00:00')
// 输出结果进行对比
console.log(time) // Fri Jul 12 2019 00:00:00 GMT+0800 (中国标准时间)
console.log(copyTime) // Wed Dec 12 2012 00:00:00 GMT+0800 (中国标准时间)
5 正则
var rule = new RegExp('abc');
// 用 “=” 进行赋值
var newRule = rule;
newRule = new RegExp('hello');
// 输出结果进行对比
console.log(rule) // /abc/
console.log(newRule) // /hello/
小结:
1 当拷贝的数据类型为对象或者数组时,拷贝的数据发生变化的时,数据源也会发生变化;
2 当类型为Date,function,Regexp时,拷贝的数据发生变化时,数据源不会发生变化
- 通过Object.assign()对对象进行拷贝(只能实现浅拷贝)
var obj = {name:'张三'};
// 拷贝数据
var copyObj = Object.assign(obj); // 此时只改变了对象的指向;
copyObj.name = "李四";
// 输出结果进行对比
console.log(obj) // {name: "李四"}
console.log(copyObj) // {name: "李四"}
// 拷贝数据
copyObj = Object.assign({},obj); // 浅拷贝 拷贝了对象的值;
copyObj.name = "王五";
// 输出结果进行对比
console.log(obj); // {name: "张三"}
console.log(copyObj); // {name: "王五"}
小结: Object.assign()在实现拷贝对象时,需要传入“{}”作为参数,才能对复杂数据类型实现浅拷贝
- 通过Object.assign()对数组进行拷贝(只能实现浅拷贝)
var arr = ['a','b','c'];
// 拷贝数据
var copyArr = Object.assign(arr); // 此时值改变了对象的指向;
copyArr[0] = 'change';
// 输出结果进行对比
console.log(arr); //["change", "b", "c"]
console.log(copyArr) // ["change", "b", "c"]
// 拷贝数据
copyArr = Object.assign([],arr); // 浅拷贝 拷贝了数组的值;
copyArr[0] = "change";
// 输出结果进行对比
console.log(arr) // ["a", "b", "c"]
console.log(copyArr); // ["change", "b", "c"]
小结: Object.assign()在实现拷贝对象时,需要传入“[]”作为参数,才能对复杂数据类型实现浅拷贝
注: 通过以上代码,貌似实现了对对象和数组的拷贝工作,其实是存在bug , 代码如下:
var obj = {
name:'zhangsan',
assets:{
save:10000,
salary:8000
}
}
var copyObj = Object.assign({},obj);
// 此时只能实现第一层数据的拷贝,更深层级的数据实现的都是改变了指向;
copyObj.assets.save = "999";
copyObj.name = 'lisi'
// 输出结果 进行对比
console.log(JSON.stringify(obj))
// {"name":"zhangsan","assets":{"save":"999","salary":8000}}
console.log(JSON.stringify(copyObj))
// {"name":"lisi","assets":{"save":"999","salary":8000}}
var arr = [1,2,{data:'100'}];
// 拷贝数据
var copyArr = Object.assign([],arr);
// 修改拷贝后的数据
copyArr[0] = "hellowold";
copyArr[2].data = "一百万";
// 输出结果 进行对比
console.log(JSON.stringify(arr)); // [1,2,{"data":"一百万"}]
console.log(JSON.stringify(copyArr)) // ["hellowold",2,{"data":"一百万"}]
小结: 通过以上代码发现,Object.assign()方法只是实现了对复杂数据类型的第一层数据的拷贝,对于更深层次的数据只是改变了指向;
- 通过 for in 来实现拷贝
function copyData (a) { // 只适用于只有一层数据结构的对象或数组;
var res = a.constructor()
console.log(res)
for(var key in a) {
if(a.hasOwnProperty(key)) {
res[key] = a[key];
}
}
return res;
}
// 示例
// 1 对象数据类型
var obj = {name:'wangfei'};
// 调用方法 进行赋值
var copyObj = copyData(obj);
copyObj.name = '吴彦祖';
// 输出结果 进行对比
console.log(obj); // {name: "wangfei"}
console.log(copyObj) // {name: "吴彦祖"}
// 2 数组数据类型
var arr = ['a','b','c'];
// 调用方法 进行赋值
var copyArr = copyData(arr);
copyArr[0] = 1000;
// 输出结果 进行对比
console.log(arr) // ["a", "b", "c"]
console.log(copyArr) // [1000, "b", "c"]
- 注:通过上面的代码实现,貌似封装的方法可以实现复杂数据类型的拷贝工作,其实是有缺陷的,当数组结构包含两层或多层复杂数据类型时,就无法完成拷贝工作了 ,代码如下:
var arr = [1,2,{name:'zhangwuji'}];
// 拷贝数据
var copyArr = copyData(arr);
copyArr[0] = '变化';
copyArr[2].name = '张无忌';
// 输出结果 进行对比
console.log(JSON.stringify(arr)) // [1,2,{"name":"张无忌"}]
console.log(JSON.stringify(copyArr)) // ["变化",2,{"name":"张无忌"}]
- 通过JSON.stringify()和JSON.parse()实现对象的拷贝(深拷贝,不完善)
// 将JSON.stringify() 和 JSON.parse()封装成一个方法;
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
var arr = [1,2,{name:'zhangwuji'}];
// 拷贝数据
var res = deepCopy(arr); // 此时木问题;
res[0] = '哈哈';
res[2].name = "张无忌";
// 输出结果,进行对比
console.log(JSON.stringify(arr)) // [1,2,{"name":"zhangwuji"}]
console.log(JSON.stringify(res)) // [1,2,{"name":"zhangwuji"}]
var obj = {
name:'zhangsan',
assets:{
save:10000,
salary:8000
}
}
// 拷贝数据
var copyObj = deepCopy(obj); // 此时 木问题 !!!!
copyObj.name = '王菲';
copyObj.assets.save = "一千万";
// 输出结果,进行对比
console.log(JSON.stringify(obj))
// {"name":"zhangsan","assets":{"save":10000,"salary":8000}}
console.log(JSON.stringify(copyObj))
// {"name":"王菲","assets":{"save":"一千万","salary":8000}}
- 注: .上述的方法会忽略值为function以及undefined的字段,而且对date类型的支持也不太友好; 代码如下:
var obj = {
name:'wangfei',
fun:function(){
console.log('fun')
},
data:undefined,
}
// 拷贝数据
var copyObj = deepCopy(obj);
// 输出结果,进行对比
console.log(obj)
console.log(JSON.stringify(obj)) // {"name":"wangfei"}
console.log(copyObj.fun) // undefined
console.log(copyObj.data) // undefined
console.log(JSON.stringify(copyObj)) // {"name":"wangfei"}
总结: 通过以上分析,可以看出 ,这几种方法都存在一定的局限性;还是在文章开头封装的方法,能够比较完善的实现js中数据(简单数据类型&复杂数据类型)的拷贝工作;