资讯专栏INFORMATION COLUMN

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

RyanQ / 1316人阅读

摘要:而在这个运算符的相关用例中,往往会涉及到其他知识点,深拷贝和浅拷贝就是其中之一。即对象的浅拷贝会对主对象的值进行拷贝,而该值有可能是一个指针,指向内存中的同一个对象。,可以看到深拷贝和浅拷贝是对复制引用类型变量而言的。

在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不允许直接访问内存中的位置,换句话说,不能直接操作对象的内存空间。
因此,在操作对象时,我们实际上是在操作对象的引用,而不是实际的对象

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

在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())也并不是万能的,比如对象的属性是undefined,function()时:

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有两种数据类型:基本数据类型引用数据类型

基本数据类型:String,Number,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;
这两种操作可不是一码事。。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104034.html

相关文章

  • javascript拷贝VS拷贝

    摘要:深拷贝浅拷贝本文主要对深拷贝浅拷贝的解释及实现做一下简单记录。之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。但注意,只能做一层属性的浅拷贝。 深拷贝VS浅拷贝 本文主要对深拷贝&浅拷贝的解释及实现做一下简单记录。原文链接,欢迎star。 之所以会有深拷贝与浅拷贝之分,是因为不同数据类型的数据在内存中的存储区域不一样。 堆和栈是计算机中划分出来用来存储的...

    Nekron 评论0 收藏0
  • 拷贝 vs 拷贝

    摘要:那么如何切断和之间的关系呢,可以拷贝一份的数据,根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝。 深拷贝 vs 浅拷贝 深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据。 // 基本类型 var a = ...

    sugarmo 评论0 收藏0
  • 拷贝

    摘要:深复制实现代码如下第一种方法通过递归解析解决第二种方法通过解析解决作者六师兄链接原生深拷贝的实现处理未输入新对象的情况通过方法构造新的对象 深浅拷贝针对的是 对象类型,如果是字符串的数组用[...arr],还是不会影响 要区分针对数组的深浅拷贝(默认情况为里面没有对象的数组),与针对对象的深浅拷贝 JavaScript数组深拷贝和浅拷贝的两种方法 let a1 = [1, 2]; ...

    Karrdy 评论0 收藏0
  • 拷贝拷贝

    摘要:二浅拷贝与深拷贝深拷贝和浅拷贝是只针对和这样的引用数据类型的。浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。对于字符串数字及布尔值来说不是或者对象,会拷贝这些值到新的数组里。 一、数据类型 数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。 基本数据类型的特点:直...

    hzc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<