乐趣区

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

【前端芝士树】浅拷贝、深拷贝以及 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}
所以深拷贝问题的出现就是为了解决引用类型的数据的浅拷贝特性
实现对象深拷贝的几种方法

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 和其他内置类型在进行序列化时会丢失。

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() 方法属于深拷贝吗?
这个我都被弄糊涂了,网上找了些资料才捋清了一下。
对于一维数组而言:

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

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() 考察点是 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。

退出移动版