【前端芝士树】浅拷贝、深拷贝以及Object.assign()的作用、克隆对象、复制数组

柯里薄雾
• 阅读 4389

【前端芝士树】浅拷贝、深拷贝以及Object.assign()的作用

首先还是得回到Javascript的基本数据类型。

值类型[深拷贝]:数值Num、布尔值Boolean、字符串String、null、undefined。

基本类型值是指在栈内存保存的简单数据段,在复制基本类型值的时候,会开辟出一个新的内存空间,将值复制到新的内存空间,举个栗子:

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

引用类型[浅拷贝]:对象、数组、函数等。

用类型值是保存在堆内存中的对象,变量保存的只是指向该内存的地址,在复制引用类型值的时候,其实只复制了指向该内存的地址,举个栗子:

var a={b:1}
var a2 = a;
a2.b = 2;
console.log(a)  // 输出 {b: 2}

所以深拷贝问题的出现就是为了解决引用类型的数据的浅拷贝特性

实现对象深拷贝的几种方法

  1. JSON.parse() && JSON.stringfy()
    将该对象转换为其 JSON 字符串表示形式,然后将其解析回对象。这感觉有点太过简单了,但它确实有效:

    const obj = /* ... */;
    const copy = JSON.parse(JSON.stringify(obj));

    优点是,如果没有循环对象,并且不需要保留内置类型,使用该方法皆可以获得最快的跨浏览器的克隆性能。
    这里的缺点是创建了一个临时的,可能很大的字符串,只是为了把它重新放回解析器。
    另一个缺点是这种方法不能处理循环对象,而且循环对象经常发生。
    例如,当我们构建树状数据结构,其中一个节点引用其父级,而父级又引用其子级。

    const x = {};
    const y = {x};
    x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
    const copy = JSON.parse(JSON.stringify(x)); // throws!

    另外,诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

  2. MessageChannel && postMessage 结构化克隆算法
    这种方法的缺点是它是异步的。虽然这并无大碍,但是有时候你需要使用同步的方式来深度拷贝一个对象。

    function structuralClone(obj) {
      return new Promise(resolve => {
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
      });
    }
    
    const obj = /* ... */;
    const clone = await structuralClone(obj);

Array.slice()Array.concat()方法属于深拷贝吗?

这个我都被弄糊涂了,网上找了些资料才捋清了一下。

对于一维数组而言

  1. arrayObj.slice(start, [end])

    var arr1 = ["1","2","3"];
    var arr2 = arr1.slice(0);
    arr2[1] = "9";
    console.log("数组的原始值:" + arr1 ); //1,2,3
    console.log("数组的新值:" + arr2 ); //1,9,3
  2. arrayObj.concat(arr1,arr2 ... )

    var arr1 = ["1","2","3"];
    var arr2 = arr1.concat();
    arr2[1] = "9";
    console.log("数组的原始值:" + arr1 ); //1,2,3
    console.log("数组的新值:" + arr2 );//1,9,3

那数组里面如果包含对象呢?

var arr1 = [{"name":"weifeng"},{"name":"boy"}];//原数组
var arr2 = [].concat(arr1);//拷贝数组
arr1[1].name="girl";
console.log(arr1);// [{"name":"weifeng"},{"name":"girl"}]
console.log(arr2);//[{"name":"weifeng"},{"name":"girl"}]

var a1=[["1","2","3"],"2","3"],a2;
a2=a1.slice(0);
a1[0][0]=0; //改变a1第一个元素中的第一个元素
console.log(a2[0][0]);  //影响到了a2

从上面两个例子可以看出,由于数组内部属性值为引用对象,因此使用slice和concat对对象数组的拷贝,整个拷贝还是浅拷贝,拷贝之后数组各个值的指针还是指向相同的存储地址。

Array.slice()Array.concat() 这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝!

Object.assign() 方法 以及 对象扩展操作符 ...

Object.assign() 方法

Object.assign()考察点是ES6中实现对象复制,关于Object.assign()这个函数这里有一篇文章讲得非常详细明白。

ES6提供了Object.assign(),用于合并/复制对象的属性。

Object.assign(target, source_1, ..., source_n)

下面是一个例子

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

那么Object.assign()方法是浅拷贝还是深拷贝呢?请看下面这个例子:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}

const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true 
Object.assign(target, sources...)是一个简单的拷贝对象的方式,属于浅拷贝。它接受任意数量的源对象,主要作用就是枚举它们的所有属性并分配给target

对象扩展操作符 ...

使用对象扩展操作符 ...,对象自己的可枚举属性可以被拷贝到新对象。
const obj = { a: 1, b: 2, c:{d:'d'} }
const shallowClone = { ...obj }
shallowClone.a = 'a';
shallowClone.c.d = '4';
console.log(obj); // a: 1, b: 2, c: {d: "4"}}
console.log(shallowClone); // a: "a", b: 2, c: {d: "4"}}

其他的看上去像是对象深拷贝,实质是浅拷贝的方法

const obj = { a: 1, b: 2, c:{d:'d'} }
const shallowClone = Object.keys(obj).reduce((acc, key) => (acc[key] = obj[key], acc), {});
shallowClone.a = 'a';
shallowClone.c.d = '4';
console.log(obj); // a: 1, b: 2, c: {d: "4"}}
console.log(shallowClone); // a: "a", b: 2, c: {d: "4"}}
点赞
收藏
评论区
推荐文章
翼
4年前
ES6的解构赋值是深拷贝or浅拷贝?
面试时候有面试官问到ES6的解构赋值是深拷贝还是浅拷贝?,这里做一个总结.ES6的解构赋值,大家应该都清楚,就是可以快速取出数组或者对象中的值;我们先来看一个使用案例:更多的解构赋值知识可以查看:https://es6.ruanyifeng.com/docs/destructuring那么,ES6的解构赋值到底是深拷贝还是浅拷贝呢?我们先来看一下深拷贝和浅
Wesley13 Wesley13
4年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
仔细看看,会有收获。js深浅拷贝
好好理解深浅拷贝和赋值(针对引用类型)赋值:两个对象指向同一内存地址。结果,无论是修改基本类型还是引用类型,两个对象的值都会改变。浅拷贝:两个对象指向不同的内存地址,但是他们中的引用类型数据指向同一内存地址。结果,修改引用类型,两个对象的值都会改变;修改基本类型,互不影响。深拷贝:两个对象指向不同的内存地址,他们中的引用类型也指向不同的内存地址。结果,均互不
Wesley13 Wesley13
4年前
java克隆之深拷贝与浅拷贝
版权声明:本文出自汪磊的博客,未经作者允许禁止转载。Java深拷贝与浅拷贝实际项目中用的不多,但是对于理解Java中值传递,引用传递十分重要,同时个人认为对于理解内存模型也有帮助,况且面试中也是经常问的,所以理解深拷贝与浅拷贝是十分重要的。一、Java中创建对象的方式①:与构造方法有关
晴空闲云 晴空闲云
4年前
也谈JavaScript浅拷贝和深拷贝
网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论。javascript中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝。直接赋值对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更。示
菜园前端 菜园前端
2年前
带你了解JS对象的浅拷贝和深拷贝
以下主要介绍了正常情况下的拷贝、浅拷贝、深拷贝三种方式的区别。正常拷贝:复制一个对象,它们的内存地址是相同的浅拷贝:拷贝对象的第一层属性深拷贝:拷贝对象多层的属性正常拷贝假设我们要复制一个对象,如果不对其进行深拷贝,那么改变其中一个对象后,另外一个对象也会
Souleigh ✨ Souleigh ✨
5年前
实现深拷贝的多种方式
实现深拷贝的多种方式简单来说,深拷贝主要是将另一个对象的属性值拷贝过来之后,另一个对象的属性值并不受到影响,因为此时它自己在堆中开辟了自己的内存区域,不受外界干扰。浅拷贝主要拷贝的是对象的引用值,当改变对象的值,另一个对象的值也会发生变化。1.简单深拷贝(一层浅拷贝)①for循环拷贝//只复制第一层的浅拷贝javascriptfunc
Wesley13 Wesley13
4年前
Java深拷贝和浅拷贝
1.浅复制与深复制概念⑴浅拷贝(浅克隆)   复制出来的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。⑵深拷贝(深克隆)   复制出来的所有变量都含有与原来的对象相同的值,那些引用其他对象的变量将指向复制出来的新对象,而不再是原有的那些被引用的对象。换言之,深复制
Wesley13 Wesley13
4年前
Java对象的浅拷贝和深拷贝&&String类型的赋值
Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、方法传参或返回值时,会有值传递和引用(地址)传递的差别。浅拷贝(ShallowCopy):①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,
Stella981 Stella981
4年前
JavaScript的深拷贝和浅拷贝
一、数据类型数据分为基本数据类型(String,Number,Boolean,Null,Undefined,Symbol)和对象数据类型。、1.基本数据类型的特点:直接存储在栈(stack)中的数据2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据放在堆内存里。引用数据类型在栈中存储了指针,该指针指向堆中该实
Stella981 Stella981
4年前
JavaScript基础心法——深拷贝和浅拷贝
!(https://oscimg.oschina.net/oscnet/c131215a5aaaeb7909d7398688df6ea6dcd.png)浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。前言说到深浅拷贝,必须先