深拷贝和浅拷贝都是针对的引用类型,对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
那么如何切断a和b之间的关系呢,可以拷贝一份a的数据,根据拷贝的层级不同可以分为浅拷贝
和深拷贝
,浅拷贝就是只进行一层拷贝
,深拷贝就是无限层级拷贝
深拷贝
深拷贝的问题其实可以分解成两个问题,浅拷贝+递归
假设要写个function
实现深拷贝需要注意的问题
- 对参数的检验
- 判断是否对象的逻辑要严谨
- 考虑数组的兼容
- 对Symbol,function等数据类型copy问题
- 递归爆栈问题
- 循环引用问题
一、递归实现
export function clone(x) {
if (/*如果不是引用类型直接返回*/) {
return x;
}
const t = checkType(x);//检测试array还是object
let res;
if (t === 'array') {
res = [];
for (let i = 0; i < x.length; i++) {
// 避免一层死循环 a.b = a
res[i] = x[i] === x ? res: clone(x[i]);
}
} else if (t === 'object') {
res = {};
for(let key in x) {
if (hasOwnProp(x, key)) {
// 避免一层死循环 a.b = a
res[key] = x[key] === x ? res : clone(x[key]);
}
}
}
return res;
}
此方法递归层数过大之后会有爆栈问题,// Maximum call stack size exceeded
二、JSON 方法实现
// 通过JSON深拷贝
export function cloneJSON(x, errOrDef = true) {
if (/*如果不是引用类型直接返回*/) {
return x;
}
try {
return JSON.parse(JSON.stringify(x));
} catch(e) {
if (errOrDef === true) {
throw e;
} else {
console.error('cloneJSON error: ' + e.message);
return errOrDef;
}
}
}
- 也有爆栈问题//// Maximum call stack size exceeded
- 循环引用情况直接报错// // Uncaught TypeError: Converting circular structure to JSON
undefined
、任意的function
以及symbol
值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)- symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
三、递归爆栈(消除递归,使用循环)
// 循环
export function cloneLoop(x) {
const t = checkType(x);
let root = x;
if (t === 'array') {
root = [];
} else if (t === 'object') {
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;
const tt = type(data);
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = tt === 'array' ? [] : {};
}
if (tt === 'array') {
for (let i = 0; i < data.length; i++) {
// 避免一层死循环 a.b = a
if (data[i] === data) {
res[i] = res;
} else if (isClone(data[i])) {
// 下一次循环
loopList.push({
parent: res,
key: i,
data: data[i],
});
} else {
res[i] = data[i];
}
}
} else if (tt === 'object'){
for(let k in data) {
if (hasOwnProp(data, k)) {
// 避免一层死循环 a.b = a
if (data[k] === data) {
res[k] = res;
} else if (isClone(data[k])) {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
}
return root;
}
1.无爆栈问题
2.依然无法解决循环引用问题
四、copy缓存对象(解决循环引用问题)
const UNIQUE_KEY = 'bee' + (new Date).getTime();
// weakmap:处理对象关联引用
class SimpleWeakmap {
constructor() {
this.cacheArray = [];
}
set(key, value) {
this.cacheArray.push(key);
key[UNIQUE_KEY] = value;
}
get(key) {
return key[UNIQUE_KEY];
}
clear() {
for (let i = 0; i < this.cacheArray.length; i++) {
let key = this.cacheArray[i];
delete key[UNIQUE_KEY];
}
this.cacheArray.length = 0;
}
}
function getWeakMap(){
let result;
if(typeof WeakMap !== 'undefined' && type(WeakMap)== 'function'){
result = new WeakMap();
if(type(result) == 'weakmap'){
return result;
}
}
result = new SimpleWeakmap();
return result;
}
export function cloneForce(x) {
const uniqueData = getWeakMap();
const t = type(x);
let root = x;
if (t === 'array') {
root = [];
} else if (t === 'object') {
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 source = node.data;
const tt = type(source);
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let target = parent;
if (typeof key !== 'undefined') {
target = parent[key] = tt === 'array' ? [] : {};
}
// 复杂数据需要缓存操作
if (isClone(source)) {
// 命中缓存,直接返回缓存数据
let uniqueTarget = uniqueData.get(source);
if (uniqueTarget) {
parent[key] = uniqueTarget;
continue; // 中断本次循环
}
// 未命中缓存,保存到缓存
uniqueData.set(source, target);
}
if (tt === 'array') {
for (let i = 0; i < source.length; i++) {
if (isClone(source[i])) {
// 下一次循环
loopList.push({
parent: target,
key: i,
data: source[i],
});
} else {
target[i] = source[i];
}
}
} else if (tt === 'object'){
for(let k in source) {
if (hasOwnProp(source, k)) {
if(k === UNIQUE_KEY) continue;
if (isClone(source[k])) {
// 下一次循环
loopList.push({
parent: target,
key: k,
data: source[k],
});
} else {
target[k] = source[k];
}
}
}
}
}
uniqueData.clear && uniqueData.clear();
return root;
}
网友评论