既然查询js拷贝相关的内容,那么就自作主张的认为屏幕前的你已经知道js的基本数据类型和引用数据类型
,这里也就不再过多讲解。我们只需知道对于基本数据类型的拷贝实质是数据值的拷贝,并没有深浅拷贝的区别;浅拷贝和 深拷贝 针对的是JS中的引用类型,浅拷贝复制数据的引用地址,修改拷贝后的数据,原数据也会随之改变。只有深拷贝才是真正地对数据的拷贝,并且不会影响到原数据。
惯例,先看代码例子:
const originArr = ["test", 1, true];
const originObj = { a: 1, b: "test", c: true };
const cloneArr = originArr;
const cloneObj = originObj;
cloneArr.push("hello");
cloneArr[2] = false;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);
cloneObj["d"] = "world";
cloneObj["b"] = false;
console.log("cloneObj:", cloneObj);
console.log("originObj:", originObj);
console.log(cloneObj === originObj);
结果输出为:
cloneArr: [ 'test', 1, false, 'hello' ]
originArr: [ 'test', 1, false, 'hello' ]
true
cloneObj: { a: 1, b: false, c: true, d: 'world' }
originObj: { a: 1, b: false, c: true, d: 'world' }
true
总结出两点:
const
方式声明赋值的引用数据类型
的常量的值(并非引用地址)其实是可以改变的;浅拷贝
:引用数据类型通过=
等号赋值方式拷贝,其实是对引用地址的拷贝,拷贝前后的数据指向同一个地址; 深拷贝
就是对目标的完全拷贝,不同于浅拷贝(引用地址的复制),深拷贝是对引用地址指向的堆内存数据的拷贝,并且在栈中创建新的引用地址指向拷贝后的堆数据
。进行了深拷贝,新老数据老死不相往来,互不影响。
目前实现深拷贝的方法不多,主要是两种:
const originArr = ["test", 1, true, { i: 1, j: "hhh" }];
const originObj = { a: 1, b: "test", c: true, e: { x: 1, y: "hhh" } };
const cloneArr = JSON.parse(JSON.stringify(originArr));
const cloneObj = JSON.parse(JSON.stringify(originObj));
cloneArr.push("hello");
cloneArr[2] = false;
cloneArr[3]["i"] = 2;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);
cloneObj["d"] = "world";
cloneObj["b"] = false;
cloneObj["e"]["x"] = 3;
console.log("cloneObj:", cloneObj);
console.log("originObj:", originObj);
console.log(cloneObj === originObj);
结果输出:
cloneArr: [ 'test', 1, false, { i: 2, j: 'hhh' }, 'hello' ]
originArr: [ 'test', 1, true, { i: 1, j: 'hhh' } ]
false
cloneObj: { a: 1, b: false, c: true, e: { x: 3, y: 'hhh' }, d: 'world' }
originObj: { a: 1, b: 'test', c: true, e: { x: 1, y: 'hhh' } }
false
从结果中可以看到不管是数组和对象,通JSON的stringify和parse转换
后,对拷贝的数组和对象的属性和元素进行修改和增添,无论是多少层的引用数据的改变,原数据都不受影响(记住这句话,后面有用)!
注意
:JSON的parse()、stringify()对数据的转换也是有限制的,对于undified、function、symbol
类型数据,在转换过程中会被忽略!而function
在对象中是十分常见的!所以对于该方法,使用的时候也需要十分注意!
const fucObj = {
f: function () {
console.log(123);
},
};
const cloneObj = JSON.parse(JSON.stringify(fucObj));
console.log("fucObj:", fucObj);
console.log("cloneObj:", cloneObj);
结果输出为:
fucObj: { f: [Function: f] }
cloneObj: {}
递归大家都懂吧,简单除暴,对每一层的数据都进行一次创建对象和对象赋值操作:
const fucObj = {
f: function () {
console.log(123);
},
};
function deepClone(data) {
// 判断拷贝的目标类型
const cloneData = data.constructor === Array ? [] : {};
// 遍历目标
for (let keys in data) {
//判断是否有遍历的属性值
if (data.hasOwnProperty(keys)) {
//如果是对象,或者是数组
if (data[keys] && typeof data[keys] === "object") {
// 进一步判断对象的属性值的类型
cloneData[keys] = data[keys].constructor === Array ? [] : {};
//如果是对象或者数组类型继续遍历
cloneData[keys] = deepClone(data[keys]);
} else {
// 如果不是,就直接赋值
cloneData[keys] = data[keys];
}
}
}
return cloneData;
}
console.log(deepClone(fucObj));
输出结果:
{ f: [Function: f] }
可以看出,通过遍历也可以实现数据的深拷贝!
对于首层浅拷贝
,我们更愿意称之为首层深拷贝
,因为其表达的意思是对引用数据类型进行深拷贝时,只有第一层引用进行了深拷贝,其余层引用仍是浅拷贝!
这样的实现方式js中到处都是:
...
const originArr = ["test", 1, true, { i: 1, j: "hhh" }];
const cloneArr = originArr.concat();
cloneArr.push("hello");
cloneArr[2] = false;
cloneArr[3]["i"] = 2;
console.log("cloneArr:", cloneArr);
console.log("originArr:", originArr);
console.log(cloneArr === originArr);
结果输出:
cloneArr: [ 'test', 1, false, { i: 2, j: 'hhh' }, 'hello' ]
originArr: [ 'test', 1, true, { i: 2, j: 'hhh' } ]
false
从结果我们不难看出,通过concat
方法拷贝的数组cloneArr
,修改cloneArr
的第一层数据并不影响原数据originArr
,但是修改cloneArr
的第二层数据可以看到原数据originArr
的数据也跟着一起改变了。通过下图可以做个简单的分析首层浅拷贝
:
通过concat
方法,对于多层嵌套的引用数据类型,第二层数据拷贝时不会在进行深拷贝,而是直接复制了同一个引用地址,所以最终还是指向同一个堆内存地址,所有修改都会会向影响!
欢迎大家关注本人的微信公众号,微信公众号将不定期发送相应学习文章和教程