先上代码 来源
// 宽度优先遍历复制, 借助队列
/**
* 整个算法思路为, 利用队列存储当前需要复制的对象记录
* 取出队列的的元素,遍历复制属性
* 1. 如果属性不为对象引用 那么直接赋值
*
* 2. 如果为对象. 那么先创建一个新对象 并把这个对象利用key value存储当前复制的对象上面
* 然后把当前创建的新对象加入到复制队列中 currentCopyElement[key] = {};
* 这样的话,当复制队列元素进行复制的时候,由于是引用,则当前对象的属性值也会改变
*
* 为了这里还用了 srcVisitedQueue 这个是用来记录访问过(复制)的对象的,它用于判断是否有循环
* 引用,那如果单单是为了判断是否为循环引用的话,那么为何在复制队列上面还需要一个 copyVisitedQueue
* 因为复制队列上面 所有的对象都是新创建的,不可能说如果有循环引用的话,就直接拿旧队列上面的对象
*/
function deepCopy(obj) {
var newObj = {},
/**创建4个必须的队列 */
srcQueue = [obj], srcVisitedQueue = [],
copyQueue = [newObj], copyVisitedQueue = [];
while (srcQueue.length > 0) {
//移出当前需要复制的对象
var currentSrcElement = srcQueue.shift(),
currentCopyElement = copyQueue.shift();
//加入到已经访问过的队列中
srcVisitedQueue.push(currentSrcElement);
copyVisitedQueue.push(currentCopyElement);
//进行属性的遍历 然后复制
for (var key in currentSrcElement) {
//不是对象 直接复制
if (typeof currentCopyElement[key] !== 'object') {
currentCopyElement[key] = currentSrcElement[key];
//如果是对象的话 分为两种情况
} else {
//判断是否有环
var index = srcVisitedQueue.indexOf(currentSrcElement[key]);
if (index >= 0) {
//有环的时候 直接取之前已经访问过的对象 不用重新创建
currentCopyElement[key] = copyVisitedQueue[index];
} else {
//不是环的话,新创建一个新对象,然后保存当前对象的属性,再把当前创建的对象
//放入到复制队列中,进行属性的填充
srcQueue.push(currentSrcElement[key]);
currentCopyElement[key] = {};
copyQueue.push(currentCopyElement[key]);
}
}
}
}
return newObj;
}
// Test case
// 1. 只含有简单类型的Object{a: 1, b:2} => pass
// 2. 简单类型和复杂类型同时存在的情况下的Object => pass:
// var obj1 = {
// a: 1,
// b: 2,
// c: {
// d: 3,
// e: {
// f: 4,
// g: 5
// }
// },
// h: {
// i: 6,
// j: 7
// }
// };
// 3. 有环的情况下的Object => pass:
// var obj1 = {
// a: 1,
// b: 2,
// c: obj1
// };
相关的注释已经直接放入代码,当然不否认源代码还有不少缺少考虑的东西,但是不能否认这个深度复制和判断是否有环的做法的精妙
网友评论