JavaScript·随记 深拷贝 vs. 浅拷贝

发布时间:2019-08-13 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了JavaScript·随记 深拷贝 vs. 浅拷贝脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

在ES6的系列文章中,基本都会提到Spread——扩展运算符(...)。而在这个运算符的相关用例中,往往会涉及到其他知识点,深拷贝浅拷贝就是其中之一。

背景知识

在讨论深拷贝浅拷贝之前,我们先看一个例子:

let a = 'hi';
b = a;
b = 'hello';
console.LOG(a);
// 'hi'

let arr1 = [1,2,3];
arr2 = arr1;
arr2[0] = 0;
console.log(arr1);
// [0,2,3]

可以看到:为不同的js变量复制值时,结果是不同的。把字符串a的值复制给b改变b的值不会影响a的值,而把数组arr1的值复制给arr2时,改变arr2的值,arr1的值也跟着改变了。

这是因为js存在两种不同数据类型的值:基本类型值引用类型值
在访问这两种类型的变量时,其访问方式是不同的。在这里,先记一下结论:

  • 基本数据类型是按值访问的
  • 引用数据类型是按引用访问的
    (实际上这种说法并不严密,为便于理解,我们先这么记)

什么意思
JavaScript不允许直接访问内存中的位置,换句话说,不能直接操作对象的内存空间。
因此,在操作对象时,我们实际上是在操作对象的引用,而不是实际的对象

从一个变量向另一个变量复制值时(不管是复制基本类型还是引用类型),都会先为这个新变量分配一个空间,然后把该值复制到新创建的这个空间里。
不同的是,在复制引用类型的值时,实际上复制过去的是一个指针,示例图如下:

JavaScript·随记    深拷贝 vs. 浅拷贝

在js中,除了7种基本类型外,其他的都是引用类型,比如Object,Array,Function,所以不难理解:

let obj1 = {name:'hx',age:18};
let obj2 = obj1;
obj2.age = 0;
console.log(obj1);
// {name:'hx',age:0}
“引用类型的值是按引用访问的”不严密在:当复制保存着对象的某个变量时,操作的是对象的引用;而当为对象添加属性时,操作的是实际的对象。 ——灵图社区

深拷贝 vs. 浅拷贝

我们先来看一下概念:

浅拷贝:

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对主对象的进行拷贝,而该值有可能是一个指针,指向内存中的同一个对象。

深拷贝:

深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。所以对一个对象的修改完全不会影响到另一个对象。

OK,可以看到深拷贝浅拷贝是对“复制引用类型变量”而言的。事实上,也只有在引用类型中才有讨论两者区别的意义,对于基本数据类型,怎么拷都是“深拷贝”。

浅拷贝就不说了,=就是浅拷贝,那么如何实现深拷贝呢?
对于ObjectArray两种类型,我们分别举例:

Object

首先是assign(),看代码:

let obj = {name:'hx',age:18};
let copyObj = Object.assign({},obj);
copyObj.name = 'H.Lucas';
console.log(obj);
// {name:'hx', age:18} 

Emm,貌似是深拷贝哈,那要是二维对象呢?

let obj = {name:'zj',attr:{age:18, nickname:'Z.Crystal'}}
let copyObj = Object.assign({},obj);
copyObj.attr.nickname = 'erni';
console.log(obj);
// {name:'zj',attr:{age:18, nickname:'erni'}}

好吧,翻车了,看来assign只能实现一维对象的深拷贝。

然后是扩展运算符...,看代码:

let obj = {name:'hx',age:18};
let copyObj = {...obj};
copyObj.name = 'H.Lucas';
console.log(obj);
// {name:'hx', age:18} 

嗯,深拷贝哈,也来个二维对象试试:

let obj = {name:'zj',attr:{age:18, nickname:'Z.Crystal'}}
let copyObj = {...obj};
copyObj.attr.nickname = 'erni';
console.log(obj);
// {name:'zj',attr:{age:18, nickname:'erni'}}

好吧,也炸了,看来都实现不了多维对象的深拷贝。

不过这里还是推崇一下...,为什么?看两段代码:

let obj1 = {a:1,b:2}
let obj2 = {b:3,c:4}
// 构建一个新对象obj,值是obj1和obj2的集合
let obj = Object.assign(obj1,obj2)
obj.b = 100
console.log(obj1)
console.log(obj2)
// {a: 1, b: 100, c: 4}
// {b: 3, c: 4}
let obj1 = {a:1,b:2}
let obj2 = {b:3,c:4}
// 构建一个新对象obj,值是obj1和obj2的集合
let obj = {...obj1,...obj2}
obj.b = 100
console.log(obj1)
console.log(obj2)
// {a: 1, b: 2}
// {b: 3, c: 4}

在第一段代码中,执行完Object.assign()时,obj1已经改变了,而且改变组合出来的obj时,obj1还会再改变。实际上我只想组合出一个完全独立的obj来,可以肆意改变它,而不影响原始数据(想一下纯函数的实现,以及redux等)。

Array

Emm,数组拷贝能想到哪些
slice()concat()Array.From()...
这里就不一个一个试了,先给出结论吧:
都只能实现一维数组的深拷贝

看个例子:

let arr1 = [1, 2], arr2 = [...arr1];
console.log(arr1); // [1, 2]
console.log(arr2); // [1, 2]
arr2[0] = 3;
console.log(arr1); // [1, 2]
console.log(arr2); // [3, 2]

let arr3 = [1, 2, [3, 4]], arr4 = [...arr3];
console.log(arr3); // [1, 2, [3, 4]]
console.log(arr4); // [1, 2, [3, 4]]
arr4[2][1] = 5; 
console.log(arr3); // [1, 2, [3, 5]]
console.log(arr4); // [1, 2, [3, 5]]

好吧,那js里到底有没有不限条件的深拷贝方法呢?看下这个:

let obj1 = {x:1, y:{z:1}};
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2)
// {x:1, y:{z:1}}

// 改一下obj2,看看会不会影响obj1
obj2.y.z = 2;

console.log(obj1)
// {x:1, y:{z:1}}
// 可以,obj1没有受到obj2的影响

简单粗暴吧?不过JSON.parse(JSON.stringify())也并不是万能的,比如对象的属性是undefinedfunction()时:

let obj1 = {
  x: 1,
  y: undefined,
  z: () => console.log('lalala')
};

let obj2 = JSON.parse(JSON.stringify(obj1));

console.log(obj2);
// {x: 1}
// 源对象的属性y和z都丢失了,更别说深拷贝了

那数组呢?有没有不限条件的深拷贝方法,哪怕有个类似的简单粗暴的不完全体也行啊。
了解只有一种方法,建一个空数组,然后一级一级遍历原数组并填充到里面吧。

Summary

  • JavaScript有两种数据类型:基本数据类型引用数据类型

    • 基本数据类型:StringNumber,Boolean,Undefined,Null,Symbol
    • 引用数据类型:Object,Array,Function
  • JavaScript不允许直接访问内存中的位置,因此我们操作的只是对象引用,而不是实际的对象;
  • 深拷贝浅拷贝是针对引用数据类型而言的;
  • JavaScript暂时还没有实现维数组/对象深拷贝的内置方法(还是说,有,但是我不知道。。)

对了,补充一个知识点:
有人说,我家=也可以实现一级深拷贝啊,你看:

let arr1 = {a:1,b:2};
arr2 = arr1;
arr2 = {a:0,b:1};
console.log(arr2); // {a:0, b:2}
console.log(arr1); // {a:1, b:2}

额。。不带你这么玩的。。
arr2 = {a:0,b:1}; vs. arr2.a = 0;
这两种操作可不是一码事。。

脚本宝典总结

以上是脚本宝典为你收集整理的JavaScript·随记 深拷贝 vs. 浅拷贝全部内容,希望文章能够帮你解决JavaScript·随记 深拷贝 vs. 浅拷贝所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。