本文主要阅读对象:对深浅拷贝印象模糊对初级前端,想对js深浅拷贝聊一聊的中级前端。
若是是对这些有完整对认知体系和解决方法的大佬,能够选择略过。
复制代码
正文: 讨论深浅拷贝,首先要从js的基本数据类型提及: 根据 JavaScript 中的变量类型传递方式,分为值类型和引用类型, 值类型变量包括 Boolean、String、Number、Undefined、Null。
引用类型包括了 Object 类的全部, 如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按地址传递。前端
咱们来看一下这两则有什么区别:node
//eg1:
// 值类型
var a = 10
var b = a
b = 20
console.log(a) // 10
console.log(b) // 20
//解析:上述代码中,a b都是值类型,二者分别修改赋值,相互之间没有任何影响。再看引用类型的例子:
//eg2
// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}
复制代码
解析: 上述代码中,a b都是引用类型。在执行了b = a以后,修改b的属性值,a的也跟着变化。由于a和b都是引用类型,指向了同一个内存地址,即二者引用的是同一个值,所以b修改属性时,a的值随之改动。数组
**那到底什么是深浅拷贝呢?**
<br />解析:深浅拷贝是拷贝对象的`深度`来讲的:
<br />当你想拷贝的对象只有一级时,就是浅拷贝。
<br />当你拷贝的对象有多级的时候,就是深拷贝。
复制代码
再回到上面的那个例子: 在例子能看出来,若是直接采用“=”赋值,这种类型,当咱们改变b的值时候,a也会随之改变的,假如不容许改变a呢? 这时,深拷贝就出场了。bash
function clone(val){
if(!val && typeof val !== 'object'){
return
}
const newArr = toString.call(val) === ['object Array'] ? [] : {}
for (let item in val) {
if(typeof val[item] === 'object') {
newArr[item] = clone(item)
}else {
newArr[item] = val[item]
}
}
return newArr
}
//测试:
var a = {x: 10, y: 20}
var b = clone(a)
b.x = 100
b.y = 200
console.log(a) // {x: 10, y: 20}
console.log(b) //{x: 100, y: 200}
复制代码
解析: 对于这个深拷贝,你们看到这里应该均可以看明白:主要思路就是浅拷贝 + 递归。
这有几个点须要注意:
1. 判断接收到的参数类型。
2. 使用toString.call()判断更加严谨。
3. 考虑对其余引用类型的数据兼容(这里并无对每种类型的数据组作判断,引用类型包括了 Object 类的全部,如 Date、Array、Function 等。)微信
须要注意的是:用递归的话,会有一下几个须要特别注意的点:
1. 当数据层次很深时,容易爆栈(栈溢出) 解决办法=>
a. 消除尾递归
b. 改用循环
2. “循环引用”,会致使死循环 解决办法 =>
a. 循环检测
b. 暴力破解
第一种:数据广度特别大,层次特别深
第二种:相似下面的这种状况
var a = {}
a.a = a
clone(a) //死循环
ES6中一行代码实现深拷贝:JSON.parse(JSON.stringify(source))
注释:JSON.stringify()其实利用了“循环检测”机制
复制代码
这里给你们推荐一篇循环解决递归的方法: 能够保持拷贝数据之后的引用关系oop
function cloneForce(x) {
const uniqueList = []; // 用来去重
let root = {};
// 循环数组
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,不然拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
// 数据已经存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
continue; // 中断本次循环
}
// 数据不存在
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source: data,
target: res,
});
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
//eg1:
var a = {
a1: b,
a2: b,
}
var b = {};
a.a1 === a.a2 // true
var c = cloneForce(a);
c.a1 === c.a2 // true 引用保持一致
//eg2:
var a = {};
a.a = a;
console.log(cloneForce(a))//还能够破解循环引用
复制代码
主要思路是: 声明一个数组对象(父对象,key,value),其有值时,取其最后一个对象; 判断key有值,表明当前级下有子级,则拷贝到子元素。若是数据已经存在,则中断本次循环。数据不存在则对其拷贝。
有一点须要注意:cloneForce在对象数量不少时会出现很大的问题,若是数据量很大不适合使用cloneForce。post
有兴趣的同窗,能够前往 深拷贝的终极探索(90%的人都不知道),查看更多的细节。测试
总结:
若是以为对你有帮助,请给做者一点小小的鼓励, 点个赞或者收藏
吧。 有须要沟通的请联系我: 微信( wx9456d )
或 邮箱( allan_liu986@163.com )
ui