1. 深拷贝与浅拷贝的区别
深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人叫张三,然后使用克隆技术以张三来克隆另外一个人叫李四,这样张三和李四就是相互独立的,不管张三缺胳膊还是李四少腿了都不会影响另外一个人。
浅拷贝:指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个对象的改变都会影响到另一个对象。例如,一个人一开始叫张三,后来改名字为张老三了,可是他们还是同一个人,不管张三缺胳膊还是张老三少腿,都反应在同一个人身上。下面是浅拷贝的例子:
var arr = ["one", "two", "three"];
var arrto = arr;
arrto[1] = "test";
console.log("数组的原始值:" + arr);//one,test,three
console.log("数组的新值:" + arrto);//one,test,three
简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
2. 出现浅拷贝的原因?
题外话:基本数据类型有哪些:Number、String、Boolean、Null、Undefined、Symbol;引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。
使用这两类数据存储分别是:
a. 基本类型——名值存储在栈内存中,例如let a = 1;
当b=a复制时,栈内存会新开辟一个内存,例如这样:
所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。
b.引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了
3. 怎么实现深拷贝(重点)
a. 数组的深拷贝
方法一:js的slice函数
slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)
备注:splice():该方法向或者从数组中添加或者删除项目,返回被删除的项目。(该方法会改变原数组)
var arr = ["One","Two","Three"];
var arrtoo = arr.slice(0);
arrtoo[1] = "set Map";
console.log("数组的原始值:" + arr);//One,Two,Three
console.log("数组的新值:" + arrtoo);One,set Map,Three
方法二:js的concat方法
concat()方法用于连接两个或多个数组。
该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
var arr = ["One","Two","Three"];
var arrto = arr.concat();
arrto[1] = "set Map To";
console.log("数组的原始值:" + arr + "<br />");//数组的原始值:One,Two,Three
console.log("数组的新值:" + arrto + "<br />");//数组的新值:One,set Map To,Three
方法三:直接遍历
var array = [1, 2, 3, 4];
function copy() {
let newArray = [];
for(let item of array) {
newArray.push(item);
}
return newArray;
}
var copyArray = copy(array);
copyArray[0] = 100;
console.log(array);//1,2,3,4
console.log(copyArray);//100,2,3,4
b. 对象的深拷贝
方法一:直接遍历
var obj = {
name: ywt,
job: '学生'
}
function copy(obj) {
let newobj = {};
for(let item in obj) {
newObj[item] = obj;
}
return newObj;
}
var copyObj = copy(obj);
copyObj.name = "xxx";
console.log(obj);//{name:"ywt", job: "学生"}
console.log(copyObj);//{name:"xxx", job: object}
方法二: ES6的Object.assign
var obj = {
name: 'ywt',
job: '学生'
}
var copyObj = Object.assign({}, obj);
copyObj.name = 'xxx';
console.log(obj); // {name: "ywt", job: "学生"}
console.log(copyObj); // {name: "xxx", job: "学生"}
Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target
用法: Object.assign(target, source1, source2); 所以 copyObj = Object.assign({}, obj); 这段代码将会把obj中的一级属性都拷贝到 {}中,然后将其返回赋给copyObj。
方法三:ES6扩展运算符
var obj = {
name: 'ywt',
job: '学生'
}
var copyObj = { ...obj }
copyObj.name = 'xxx'
console.log(obj.name) // ywt
console.log(copyObj.name) // xxx
扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
对多层嵌套对象,很遗憾,上面三种方法,都会失败:
var obj = {
name: {
firstName: 'y',
lastName: 'wt'
},
job: '学生'
}
var copyObj = Object.assign({}, obj)
copyObj.name.lastName = 'xxx';
console.log(obj.name.lastName); // xxx
console.log(copyObj.name.lastName); // xxx
c. 通用深拷贝
var array = [
{number: 1},
{number: 2},
{number: 3}
];
var copyArray = JSON.parse(JSON.stringify(array));
copyArray[0].number = 100;
console.log(array);//[{number: 1}, { number: 2 }, { number: 3 }]
console.log(copyArray);// [{number: 100}, { number: 2 }, { number: 3 }]
d. 当大量深拷贝时
实际上,即使我们知道了如何在各种情况下进行深拷贝,我们也仍然面临一些问题: 深拷贝实际上是很消耗性能的。(我们可能只是希望改变新数组里的其中一个元素的时候不影响原数组,但却被迫要把整个原数组都拷贝一遍,这不是一种浪费吗?)所以,当你的项目里有大量深拷贝需求的时候,性能就可能形成了一个制约的瓶颈了。
immutable的作用:
通过immutable引入的一套API,实现:
1.在改变新的数组(对象)的时候,不改变原数组(对象)
2.在大量深拷贝操作中显著地减少性能消耗
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50