Skip to content

第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数? #10

@atheist1

Description

@atheist1

话不多说直接放代码
发现了比较多的错误,但由于最近工作有点忙,一直没来得及纠正

更改(0226)

// 工具函数
let _toString = Object.prototype.toString
let map = {
  array: 'Array',
  object: 'Object',
  function: 'Function',
  string: 'String',
  null: 'Null',
  undefined: 'Undefined',
  boolean: 'Boolean',
  number: 'Number'
}
let getType = (item) => {
  return _toString.call(item).slice(8, -1)
}
let isTypeOf = (item, type) => {
  return map[type] && map[type] === getType(item)
}

深复制 深度优先遍历

let DFSdeepClone = (obj, visitedArr = []) => {
  let _obj = {}
  if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')) {
    let index = visitedArr.indexOf(obj)
    _obj = isTypeOf(obj, 'array') ? [] : {}
    if (~index) { // 判断环状数据
      _obj = visitedArr[index]
    } else {
      visitedArr.push(obj)
      for (let item in obj) {
        _obj[item] = DFSdeepClone(obj[item], visitedArr)
      }
    }
  } else if (isTypeOf(obj, 'function')) {
    _obj = eval('(' + obj.toString() + ')');
  } else {
    _obj = obj
  }
  return _obj
}

广度优先遍历

let BFSdeepClone = (obj) => {
    let origin = [obj],
      copyObj = {},
      copy = [copyObj]
      // 去除环状数据
    let visitedQueue = [],
      visitedCopyQueue = []
    while (origin.length > 0) {
      let items = origin.shift(),
        _obj = copy.shift()
      visitedQueue.push(items)
      if (isTypeOf(items, 'object') || isTypeOf(items, 'array')) {
        for (let item in items) {
          let val = items[item]
          if (isTypeOf(val, 'object')) {
            let index = visitedQueue.indexOf(val)
            if (!~index) {
              _obj[item] = {}
                //下次while循环使用给空对象提供数据
              origin.push(val)
                // 推入引用对象
              copy.push(_obj[item])
            } else {
              _obj[item] = visitedCopyQueue[index]
              visitedQueue.push(_obj)
            }
          } else if (isTypeOf(val, 'array')) {
            // 数组类型在这里创建了一个空数组
            _obj[item] = []
            origin.push(val)
            copy.push(_obj[item])
          } else if (isTypeOf(val, 'function')) {
            _obj[item] = eval('(' + val.toString() + ')');
          } else {
            _obj[item] = val
          }
        }
        // 将已经处理过的对象数据推入数组 给环状数据使用
        visitedCopyQueue.push(_obj)
      } else if (isTypeOf(items, 'function')) {
        copyObj = eval('(' + items.toString() + ')');
      } else {
        copyObj = obj
      }
    }
  return copyObj
}

测试

/**测试数据 */
// 输入 字符串String
// 预期输出String
let str = 'String'
var strCopy = DFSdeepClone(str)
var strCopy1 = BFSdeepClone(str)
console.log(strCopy, strCopy1) // String String 测试通过
// 输入 数字 -1980
// 预期输出数字 -1980
let num = -1980
var numCopy = DFSdeepClone(num)
var numCopy1 = BFSdeepClone(num)
console.log(numCopy, numCopy1) // -1980 -1980 测试通过
// 输入bool类型
// 预期输出bool类型
let bool = false
var boolCopy = DFSdeepClone(bool)
var boolCopy1 = BFSdeepClone(bool)
console.log(boolCopy, boolCopy1) //false false 测试通过
// 输入 null
// 预期输出 null
let nul = null
var nulCopy = DFSdeepClone(nul)
var nulCopy1 = BFSdeepClone(nul)
console.log(nulCopy, nulCopy1) //null null 测试通过

// 输入undefined
// 预期输出undefined
let und = undefined
var undCopy = DFSdeepClone(und)
var undCopy1 = BFSdeepClone(und)
console.log(undCopy, undCopy1) //undefined undefined 测试通过
  //输入引用类型obj
let obj = {
  a: 1,
  b: () => console.log(1),
  c: {
    d: 3,
    e: 4
  },
  f: [1, 2],
  und: undefined,
  nul: null
}
var objCopy = DFSdeepClone(obj)
var objCopy1 = BFSdeepClone(obj)
console.log(objCopy === objCopy1) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy.c) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy1.c) // 对象类型判断 false 测试通过
console.log(obj.b === objCopy1.b) // 函数类型判断 false 测试通过
console.log(obj.b === objCopy.b) // 函数类型判断 false 测试通过
console.log(obj.f === objCopy.f) // 数组类型判断 false 测试通过
console.log(obj.f === objCopy1.f) // 数组类型判断 false 测试通过
console.log(obj.nul, obj.und) // 输出null,undefined 测试通过

// 输入环状数据
// 预期不爆栈且深度复制
let circleObj = {
  foo: {
    name: function() {
      console.log(1)
    },
    bar: {
      name: 'bar',
      baz: {
        name: 'baz',
        aChild: null //待会让它指向obj.foo
      }
    }
  }
}
circleObj.foo.bar.baz.aChild = circleObj.foo
var circleObjCopy = DFSdeepClone(circleObj)
var circleObjCopy1 = BFSdeepClone(circleObj)
console.log(circleObjCopy, circleObjCopy1) // 测试通过?

ps


关于深浅拷贝的问题博主已经在他的git上有文章说过了,我就不做多的叙述
这两个方法我认为主要区别在于对于深层次以及环状数据,用深度优先遍历递归去做容易爆栈,广度优先遍历我对环状数据进行了处理,已经存在过的对象会存在数组中,下次直接赋值即可,无需继续遍历
如果出现问题欢迎讨论指出

Activity

H246802

H246802 commented on Feb 18, 2019

@H246802

@atheist1 老哥你代码最好添加代码高亮一下,这样阅读感很差。

xiaobaichiliangpi

xiaobaichiliangpi commented on Feb 19, 2019

@xiaobaichiliangpi

笔误:
map = {
number: 'Number'
}

zhengjianghao

zhengjianghao commented on Feb 21, 2019

@zhengjianghao

DFSdeepClone 方法有问题吧 字符串 clone 出来不久变数组了
浏览器console 实验一下就错了

Tairraos

Tairraos commented on Feb 22, 2019

@Tairraos

数值赋值本身就是复制,clone的时候需要复制的是array, object和function,这个代码里并没有针对function成员的复制。

oychao

oychao commented on Feb 25, 2019

@oychao

DFS没有对环状结构进行处理~

atheist1

atheist1 commented on Feb 26, 2019

@atheist1
Author

顺便说明下,这里我对es6的symbol数据以及构造函数的拷贝都没做处理,想要了解的可以看大佬的
这篇文章【进阶4-4期】Lodash是如何实现深拷贝的

LeeLejia

LeeLejia commented on Feb 26, 2019

@LeeLejia

DFSdeepClone = (obj, visitedArr = []) ,这里visitedArr如果传参的话,子层的属性不能和同层的属性做比较。只有同层的环能检测出来。
let b = {haha: 'haha'} let a = { a: {b: b}, c: {b: b} } const rs = DFSdeepClone(a) console.log(rs.a.b === rs.c.b) // false console.log(a.a.b === a.c.b) // true

WozHuang

WozHuang commented on Mar 2, 2019

@WozHuang

我只深拷贝了 Object, Array,其他的非基本类型都是浅拷贝(如果处理Set什么的就太复杂了,题目用意应该是考察遍历树和重复引用吧)

DFS用常规的递归问题不大,需要注意下重复引用的问题,不用递归的话就用栈

BFS就用队列,整体代码倒是差不多

// 如果是对象/数组,返回一个空的对象/数组,
// 都不是的话直接返回原对象
// 判断返回的对象和原有对象是否相同就可以知道是否需要继续深拷贝
// 处理其他的数据类型的话就在这里加判断
function getEmpty(o){
	if(Object.prototype.toString.call(o) === '[object Object]'){
		return {};
	}
	if(Object.prototype.toString.call(o) === '[object Array]'){
		return [];
	}
	return o;
}

function deepCopyBFS(origin){
	let queue = [];
	let map = new Map(); // 记录出现过的对象,用于处理环

	let target = getEmpty(origin);
	if(target !== origin){
		queue.push([origin, target]);
		map.set(origin, target);
	}

	while(queue.length){
		let [ori, tar] = queue.shift();
		for(let key in ori){
			// 处理环状
			if(map.get(ori[key])){
				tar[key] = map.get(ori[key]);
				continue;
			}

			tar[key] = getEmpty(ori[key]);
			if(tar[key] !== ori[key]){
				queue.push([ori[key], tar[key]]);
				map.set(ori[key], tar[key]);
			}
		}
	}

	return target;
}

function deepCopyDFS(origin){
	let stack = [];
	let map = new Map(); // 记录出现过的对象,用于处理环

	let target = getEmpty(origin);
	if(target !== origin){
		stack.push([origin, target]);
		map.set(origin, target);
	}

	while(stack.length){
		let [ori, tar] = stack.pop();
		for(let key in ori){
			// 处理环状
			if(map.get(ori[key])){
				tar[key] = map.get(ori[key]);
				continue;
			}

			tar[key] = getEmpty(ori[key]);
			if(tar[key] !== ori[key]){
				stack.push([ori[key], tar[key]]);
				map.set(ori[key], tar[key]);
			}
		}
	}

	return target;
}

// test
[deepCopyBFS, deepCopyDFS].forEach(deepCopy=>{
	console.log(deepCopy({a:1}));
	console.log(deepCopy([1,2,{a:[3,4]}]))
	console.log(deepCopy(function(){return 1;}))
	console.log(deepCopy({
		x:function(){
			return "x";
		},
		val:3,
		arr: [
			1,
			{test:1}
		]
	}))

	let circle = {};
	circle.child = circle;
	console.log(deepCopy(circle));
})
binsinger

binsinger commented on Mar 6, 2019

@binsinger

var o = {m: 2}
var obj = {
a: {b:o},
e: o
}
广度优先测试失败

atheist1

atheist1 commented on Mar 6, 2019

@atheist1
Author

var o = {m: 2}
var obj = {
a: {b:o},
e: o
}
广度优先测试失败

公司gayhub怎么老是炸,感谢提醒,已更新,在处理环状数据时我将visitedCopyQueue错用成visitedQueue了,导致上一次使用的数据是存在visitedQueue里的引用导致深复制失败

zhw2590582

zhw2590582 commented on Mar 29, 2019

@zhw2590582

笔试时会不会纸上手写深度拷贝

Emensionyu

Emensionyu commented on Apr 7, 2019

@Emensionyu

代码有点难读

yanzhang146

yanzhang146 commented on Apr 11, 2019

@yanzhang146
    function deepClone(obj){
        var temp = {};
        for(var key in obj){
            if(obj.hasOwnProperty(key)){
                temp[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
            }
        }
        return temp;
    }
changed the title [-]第六题 实现深度拷贝[/-] [+]第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数?[/+] on Apr 26, 2019
ibwei

ibwei commented on Jul 9, 2019

@ibwei

这应该是深度优先拷贝吧

let cloneObject = function(source) {
    let target = {};
    for (key in source) {
        if (source.hasOwnProperty(key)) {
            let itemType = Object.prototype.toString.call(source[key]).slice(8, -1);
            switch (itemType) {
                case "Object":
                    target[key] = cloneObject(source[key]);
                    break;
                case "Array":
                    let temp = [];
                    for (let i = 0; i < source[key].length; i++) {
                        temp.push(source[key][i]);
                    }
                    target[key] = temp;
                    break;
                default:
                    target[key] = source[key];

            }
        }
    }
    return target;
}

30 remaining items

hjiog

hjiog commented on Jul 11, 2020

@hjiog
"use strict"
// 深度优先
function deepClone(source, hash = new WeakMap()) {
    if (typeof source != "object" || source == null) return source; // 非对象或空对象返回自身
    if (hash.has(source)) {
        return hash.get(source); // 若表中存在这个键,则返回该键对应的值,解决克隆时循环引用的问题
    }
    let target = Array.isArray(source) ? [] : {}; // 识别是对象还是数组
    hash.set(source, target);
    Reflect.ownKeys(source).forEach((key) => { // 这里用Reflect.ownKeys而不用for...in迭代是因为Reflect.ownKeys可以访问Symbol属性
        /** 
         * 1. 如果使用for...in会把原型链上的其他属性也迭代出来,那么就要多层判断Object.prototype.hasOwnProperty.call(source, key),
         * 防止得到属于原型链而不属于source自身的属性,这里使用Reflect.ownKeys,因此可以省去判断
         * 2. 不直接用source.hasOwnProperty(key)是因为如果source=Object.create(null),
         那么source就没有继承Object的原型,使得source的hasOwnProperty方法为undefinde
         */
        if (typeof source[key] == "object" && source[key] != null) {
            target[key] = deepClone(source[key], hash);
        } else {
            target[key] = source[key];
        }
    });
    return target;
}

// 广度优先
function deepClone2(source) {
    if (typeof source != "object" || source == null) return source; // 非对象或空对象返回自身
    let target = Array.isArray(source) ? [] : {}; // 识别是对象还是数组
    let hash = new WeakMap();
    // 栈
    let loopList = [{
        target: target,
        source: source
    }];
    hash.set(source, target); // 注意这句不要漏,下面还有一个hash.set
    while (loopList.length) {
        // 广度优先
        let data = loopList.pop();
        let node = data.source;
        let target = data.target;
        Reflect.ownKeys(node).some((key) => { // 这里用Reflect.ownKeys而不用for...in迭代是因为Reflect.ownKeys可以访问Symbol属性
            if (typeof node[key] == "object" && node[key] != null) {
                if (hash.has(node[key])) {
                    target[key] = hash.get(node[key]);
                } else {
                    target[key] = Array.isArray(node[key]) ? [] : {}; // 识别是对象还是数组
                    loopList.push({ target: target[key], source: node[key] });
                    hash.set(node[key], target[key]);
                }
            } else {
                target[key] = node[key];
            }
        });
    }
    return target;
}


// 测试用例
var a = {
    name: "muyiy",
    book: {
        title: "You Don't Know JS",
        price: "45"
    },
    a1: undefined,
    a2: null,
    a3: 123,
    Symbols: Symbol("test")
}

a.circleRef = a;

let test2 = deepClone2(a);
// let test2 = deepClone(a);
console.log(a, test2);
zhaoyinpan2

zhaoyinpan2 commented on Jul 15, 2020

@zhaoyinpan2

function deepClone(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
var constructor = source.constructor;
if (constructor == RegExp) {
return new constructor(source)
} else if (constructor == Date) {
return new constructor(source.getTime())
} else if (constructor == Function) {
return eval('('+source.toString()+')')
} else {
if (hash.has(source)) {
return hash.get(source)
} else {
var target = new constructor;
hash.set(source, target);
for (var key in source) {
target[key] = deepClone(source[key], hash)
}
}
}
return target
}

tjwyz

tjwyz commented on Jul 16, 2020

@tjwyz

高赞默写

function getCopy (obj) {
	if (Object.prototype.toString.call(obj) == '[Object Object]') {
		return {};
	}
	if (Object.prototype.toString.call(obj) == '[Object Array]') {
		return [];
	}
	return obj;
}
function dfs(obj) {
	let stack = [];
	let ret = getCopy(obj);
	if (ret !== obj) {
		stack.push([obj, ret]);
	}
	while (stack.length) {
		let [origin, target] = stack.pop();
		for (let key in origin) {
			if (origin.hasOwnProperty(key)) {
				let tmpOrigin = origin[key];
				let tmpTarget = getCopy(tmpOrigin);
				target[key] = tmpTarget;
				if (tmpOrigin !== tmpTarget) {
					stack.push([tmpOrigin, tmpTarget])
				}
			}
		}
	}
	return ret;
}

function bfs(obj) {
	let array = [];
	let ret = getCopy(obj);
	if (ret !== obj) {
		array.push([obj, ret]);
	}
	while(array.length) {
		let [origin, target] = array.shift();
		for (let key in tmpOrigin) {
			if (tmpOrigin.hasOwnProperty(key)) {
				let tmpOrigin = target[key];
				let tmpTarget = getCopy(tmpOrigin);
				target[key] = tmpTarget;
				if (tmpOrigin !== tmpTarget) {
					array.push([tmpOrigin, tmpTarget]);
				}
			}
		}
	}
	return ret;
} 
// 递归版 dfs  能循环的就能递归... 有人爱问这个
function dfs(obj) {
	let ret = getCopy(obj);
	if (ret !== obj) {
		for (let key in obj) {
			if (obj.hasOwnProperty(key)) {
				let tmpOrigin = obj[key];
				let tmpTarget = getCopy(tmpOrigin);

				if (tmpTarget !== tmpOrigin) {
					ret[key] = dfs(tmpOrigin);
				} else {
					ret[key] = tmpTarget;
				}

			}
		}
	}
	return ret;
}
zhouatie

zhouatie commented on Jan 13, 2021

@zhouatie

引用类型只考虑Array,Object

function DFSCopy(old) {
  if (typeof old !== 'object') return old;
  const isArray = Array.isArray(old);
  const newObj = isArray ? [] : {};
  if (isArray) {
    for (let i = 0; i < old.length; i++) {
      newObj[i] = deepCopy(old[i]);
    }
  } else {
    for (let i in old) {
      newObj[i] = deepCopy(old[i]);
    }
  }
  console.log(newObj);
  return newObj;
}

function BFScopy(a) {
  const queue = [];
  const b = new a.constructor();
  queue.push([a, b]);
  while (queue.length) {
    const [oldObj, newObj] = queue.shift();
    for (let i in oldObj) {
      if (typeof oldObj[i] === 'object') {
        newObj[i] = new oldObj[i].constructor();
        queue.push([oldObj[i], newObj[i]]);
      } else {
        newObj[i] = oldObj[i];
      }
    }
  }
  return b;
}
Yayure

Yayure commented on Feb 22, 2021

@Yayure
function dfsCopy(src, copy={}) {
	Object.keys(src).forEach((key) => {
		// 需要复制其他类型请额外加判断
		copy[key] = typeof src[key] === 'object' && src !== src[key] && src[key] !== null ? dfsCopy(src[key]) : copy[key] = src[key]
	})
	return copy
}

function bfsCopy(src, copy={}, collect=[]) {
	Object.keys(src).forEach((key) => {
		// 需要复制其他类型请额外加判断
		if (typeof src[key] === 'object' && src !== src[key] && src[key] !== null) {
			collect.push({ src: src[key], copy: {} })
			copy[key] = collect[collect.length - 1].copy
		} else {
			copy[key] = src[key]
		}
	})
	if (collect.length !== 0) {
		let { src, copy } = collect.shift()
		bfsCopy(src, copy, collect)
	}
	return copy
}
bosens-China

bosens-China commented on Feb 26, 2021

@bosens-China

话不多说直接放代码
发现了比较多的错误,但由于最近工作有点忙,一直没来得及纠正

更改(0226)

// 工具函数
let _toString = Object.prototype.toString
let map = {
  array: 'Array',
  object: 'Object',
  function: 'Function',
  string: 'String',
  null: 'Null',
  undefined: 'Undefined',
  boolean: 'Boolean',
  number: 'Number'
}
let getType = (item) => {
  return _toString.call(item).slice(8, -1)
}
let isTypeOf = (item, type) => {
  return map[type] && map[type] === getType(item)
}

深复制 深度优先遍历

let DFSdeepClone = (obj, visitedArr = []) => {
  let _obj = {}
  if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')) {
    let index = visitedArr.indexOf(obj)
    _obj = isTypeOf(obj, 'array') ? [] : {}
    if (~index) { // 判断环状数据
      _obj = visitedArr[index]
    } else {
      visitedArr.push(obj)
      for (let item in obj) {
        _obj[item] = DFSdeepClone(obj[item], visitedArr)
      }
    }
  } else if (isTypeOf(obj, 'function')) {
    _obj = eval('(' + obj.toString() + ')');
  } else {
    _obj = obj
  }
  return _obj
}

广度优先遍历

let BFSdeepClone = (obj) => {
    let origin = [obj],
      copyObj = {},
      copy = [copyObj]
      // 去除环状数据
    let visitedQueue = [],
      visitedCopyQueue = []
    while (origin.length > 0) {
      let items = origin.shift(),
        _obj = copy.shift()
      visitedQueue.push(items)
      if (isTypeOf(items, 'object') || isTypeOf(items, 'array')) {
        for (let item in items) {
          let val = items[item]
          if (isTypeOf(val, 'object')) {
            let index = visitedQueue.indexOf(val)
            if (!~index) {
              _obj[item] = {}
                //下次while循环使用给空对象提供数据
              origin.push(val)
                // 推入引用对象
              copy.push(_obj[item])
            } else {
              _obj[item] = visitedCopyQueue[index]
              visitedQueue.push(_obj)
            }
          } else if (isTypeOf(val, 'array')) {
            // 数组类型在这里创建了一个空数组
            _obj[item] = []
            origin.push(val)
            copy.push(_obj[item])
          } else if (isTypeOf(val, 'function')) {
            _obj[item] = eval('(' + val.toString() + ')');
          } else {
            _obj[item] = val
          }
        }
        // 将已经处理过的对象数据推入数组 给环状数据使用
        visitedCopyQueue.push(_obj)
      } else if (isTypeOf(items, 'function')) {
        copyObj = eval('(' + items.toString() + ')');
      } else {
        copyObj = obj
      }
    }
  return copyObj
}

测试

/**测试数据 */
// 输入 字符串String
// 预期输出String
let str = 'String'
var strCopy = DFSdeepClone(str)
var strCopy1 = BFSdeepClone(str)
console.log(strCopy, strCopy1) // String String 测试通过
// 输入 数字 -1980
// 预期输出数字 -1980
let num = -1980
var numCopy = DFSdeepClone(num)
var numCopy1 = BFSdeepClone(num)
console.log(numCopy, numCopy1) // -1980 -1980 测试通过
// 输入bool类型
// 预期输出bool类型
let bool = false
var boolCopy = DFSdeepClone(bool)
var boolCopy1 = BFSdeepClone(bool)
console.log(boolCopy, boolCopy1) //false false 测试通过
// 输入 null
// 预期输出 null
let nul = null
var nulCopy = DFSdeepClone(nul)
var nulCopy1 = BFSdeepClone(nul)
console.log(nulCopy, nulCopy1) //null null 测试通过

// 输入undefined
// 预期输出undefined
let und = undefined
var undCopy = DFSdeepClone(und)
var undCopy1 = BFSdeepClone(und)
console.log(undCopy, undCopy1) //undefined undefined 测试通过
  //输入引用类型obj
let obj = {
  a: 1,
  b: () => console.log(1),
  c: {
    d: 3,
    e: 4
  },
  f: [1, 2],
  und: undefined,
  nul: null
}
var objCopy = DFSdeepClone(obj)
var objCopy1 = BFSdeepClone(obj)
console.log(objCopy === objCopy1) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy.c) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy1.c) // 对象类型判断 false 测试通过
console.log(obj.b === objCopy1.b) // 函数类型判断 false 测试通过
console.log(obj.b === objCopy.b) // 函数类型判断 false 测试通过
console.log(obj.f === objCopy.f) // 数组类型判断 false 测试通过
console.log(obj.f === objCopy1.f) // 数组类型判断 false 测试通过
console.log(obj.nul, obj.und) // 输出null,undefined 测试通过

// 输入环状数据
// 预期不爆栈且深度复制
let circleObj = {
  foo: {
    name: function() {
      console.log(1)
    },
    bar: {
      name: 'bar',
      baz: {
        name: 'baz',
        aChild: null //待会让它指向obj.foo
      }
    }
  }
}
circleObj.foo.bar.baz.aChild = circleObj.foo
var circleObjCopy = DFSdeepClone(circleObj)
var circleObjCopy1 = BFSdeepClone(circleObj)
console.log(circleObjCopy, circleObjCopy1) // 测试通过?

ps

关于深浅拷贝的问题博主已经在他的git上有文章说过了,我就不做多的叙述
这两个方法我认为主要区别在于对于深层次以及环状数据,用深度优先遍历递归去做容易爆栈,广度优先遍历我对环状数据进行了处理,已经存在过的对象会存在数组中,下次直接赋值即可,无需继续遍历
如果出现问题欢迎讨论指出

补一个测试用例
··· js
var obj = {};
obj.target = obj;

你这写的函数根本通过不了,taget直接为undefined了
Luz-Liu

Luz-Liu commented on Mar 8, 2021

@Luz-Liu

我只深拷贝了 Object, Array,其他的非基本类型都是浅拷贝(如果处理Set什么的就太复杂了,题目用意应该是考察遍历树和重复引用吧)

DFS用常规的递归问题不大,需要注意下重复引用的问题,不用递归的话就用栈

BFS就用队列,整体代码倒是差不多

// 如果是对象/数组,返回一个空的对象/数组,
// 都不是的话直接返回原对象
// 判断返回的对象和原有对象是否相同就可以知道是否需要继续深拷贝
// 处理其他的数据类型的话就在这里加判断
function getEmpty(o){
	if(Object.prototype.toString.call(o) === '[object Object]'){
		return {};
	}
	if(Object.prototype.toString.call(o) === '[object Array]'){
		return [];
	}
	return o;
}

function deepCopyBFS(origin){
	let queue = [];
	let map = new Map(); // 记录出现过的对象,用于处理环

	let target = getEmpty(origin);
	if(target !== origin){
		queue.push([origin, target]);
		map.set(origin, target);
	}

	while(queue.length){
		let [ori, tar] = queue.shift();
		for(let key in ori){
			// 处理环状
			if(map.get(ori[key])){
				tar[key] = map.get(ori[key]);
				continue;
			}

			tar[key] = getEmpty(ori[key]);
			if(tar[key] !== ori[key]){
				queue.push([ori[key], tar[key]]);
				map.set(ori[key], tar[key]);
			}
		}
	}

	return target;
}

function deepCopyDFS(origin){
	let stack = [];
	let map = new Map(); // 记录出现过的对象,用于处理环

	let target = getEmpty(origin);
	if(target !== origin){
		stack.push([origin, target]);
		map.set(origin, target);
	}

	while(stack.length){
		let [ori, tar] = stack.pop();
		for(let key in ori){
			// 处理环状
			if(map.get(ori[key])){
				tar[key] = map.get(ori[key]);
				continue;
			}

			tar[key] = getEmpty(ori[key]);
			if(tar[key] !== ori[key]){
				stack.push([ori[key], tar[key]]);
				map.set(ori[key], tar[key]);
			}
		}
	}

	return target;
}

// test
[deepCopyBFS, deepCopyDFS].forEach(deepCopy=>{
	console.log(deepCopy({a:1}));
	console.log(deepCopy([1,2,{a:[3,4]}]))
	console.log(deepCopy(function(){return 1;}))
	console.log(deepCopy({
		x:function(){
			return "x";
		},
		val:3,
		arr: [
			1,
			{test:1}
		]
	}))

	let circle = {};
	circle.child = circle;
	console.log(deepCopy(circle));
})

太强了, 这个对环状结构的处理才是正确的

wmb0412

wmb0412 commented on Jun 8, 2021

@wmb0412
//深度clone
const DFSdeepClone = (data) => {
	if (data === null) return null;
	let obj = {};
	if (Array.isArray(data)) {
		obj = [];
	}
	for (const key in data) {
		if (Object.hasOwnProperty.call(data, key)) {
			const element = data[key];
			const type = typeof element;
			if (type === 'number' || type === 'boolean' || type === 'string' || type === 'undefined' || type === 'symbol' || type == 'bigint') {
				obj[key] = element;
			} else if (type === 'function') {
				obj[key] = eval(`(${element.toString()})`);
			} else {
				obj[key] = DFSdeepClone(element)
			}
		}
	}
	return obj;
}
//广度clone
const BFSDeepClone = (data) => {
	const result = Array.isArray(data) ? [] : {};
	const stack = [[data, result]];
	while (stack.length) {
		const [originData, targetData] = stack.pop();
		for (const key in originData) {
			if (Object.hasOwnProperty.call(originData, key)) {
				const element = originData[key];
				const type = typeof element;
				if (type === 'number' || type === 'boolean' || type === 'string' || type === 'undefined' || type === 'symbol' || type == 'bigint') {
					targetData[key] = element;
				} else if (type === 'function') {
					targetData[key] = eval(`(${element.toString()})`)
				} else {
					let tmpTarget = Array.isArray(element) ? [] : {};
					targetData[key] = tmpTarget;
					stack.push([element, tmpTarget])
				}
			}
		}
	}
	return result
}
HelloGGG

HelloGGG commented on Sep 25, 2021

@HelloGGG

对象深拷贝大家可能最先想到的就是DFS递归属性,递归实现相对简单代码就不贴了,这里放下BFS实现,拷贝主要需要注意下循环引用和重复引用问题,这个可以引入一个缓存数组解决。

`/**

  • BFS深拷贝对象和数组
  • @template T
  • @param {T} obj 旧对象
  • @return {T} 新对象
    */
    function deepClone(obj) {
    // 边界处理
    if (obj.proto !== Object.prototype && obj.proto !== Array.prototype) {
    return obj;
    }
    // 结果对象
    let res = {};
    // 队列内放对应层级的属性的对象或数组
    const queue = [[res, obj]];
    // 记录已经创建的引用和对应的旧对象引用,处理循环引用和重复引用
    const hasCloneAttr = [
    {
    pre: obj, // 旧对象引用
    now: res, // 新对象引用
    },
    ];
    while (queue.length > 0) {
    const length = queue.length;
    for (let i = 0; i < length; i++) {
    const v = queue.shift();
    const keys = Object.keys(v[1]);
    for (const key of keys) {
    // 对数组和对象进行深拷贝
    if (
    v[1][key].proto === Object.prototype ||
    v[1][key].proto === Array.prototype
    ) {
    let flag = false;
    for (let item of hasCloneAttr) {
    // 旧对象存在相同引用,则不需要再创建新的引用,直接使用之前创建引用就行
    if (item.pre === v[1][key]) {
    v[0][key] = item.now;
    flag = true;
    break;
    }
    }
    if (!flag) {
    v[0][key] = v[1][key].proto === Object.prototype ? {} : [];
    hasCloneAttr.push({
    pre: v[1][key],
    now: v[0][key],
    });
    // 引用类型放入队列,下次依次取出处理
    queue.push([v[0][key], v[1][key]]);
    }
    }
    // 基本类型,直接赋值
    else {
    v[0][key] = v[1][key];
    }
    }
    }
    }
    return res;
    }`
coding-nemo

coding-nemo commented on Jan 27, 2022

@coding-nemo

话不多说直接放代码 发现了比较多的错误,但由于最近工作有点忙,一直没来得及纠正

更改(0226)

// 工具函数
let _toString = Object.prototype.toString
let map = {
  array: 'Array',
  object: 'Object',
  function: 'Function',
  string: 'String',
  null: 'Null',
  undefined: 'Undefined',
  boolean: 'Boolean',
  number: 'Number'
}
let getType = (item) => {
  return _toString.call(item).slice(8, -1)
}
let isTypeOf = (item, type) => {
  return map[type] && map[type] === getType(item)
}

深复制 深度优先遍历

let DFSdeepClone = (obj, visitedArr = []) => {
  let _obj = {}
  if (isTypeOf(obj, 'array') || isTypeOf(obj, 'object')) {
    let index = visitedArr.indexOf(obj)
    _obj = isTypeOf(obj, 'array') ? [] : {}
    if (~index) { // 判断环状数据
      _obj = visitedArr[index]
    } else {
      visitedArr.push(obj)
      for (let item in obj) {
        _obj[item] = DFSdeepClone(obj[item], visitedArr)
      }
    }
  } else if (isTypeOf(obj, 'function')) {
    _obj = eval('(' + obj.toString() + ')');
  } else {
    _obj = obj
  }
  return _obj
}

广度优先遍历

let BFSdeepClone = (obj) => {
    let origin = [obj],
      copyObj = {},
      copy = [copyObj]
      // 去除环状数据
    let visitedQueue = [],
      visitedCopyQueue = []
    while (origin.length > 0) {
      let items = origin.shift(),
        _obj = copy.shift()
      visitedQueue.push(items)
      if (isTypeOf(items, 'object') || isTypeOf(items, 'array')) {
        for (let item in items) {
          let val = items[item]
          if (isTypeOf(val, 'object')) {
            let index = visitedQueue.indexOf(val)
            if (!~index) {
              _obj[item] = {}
                //下次while循环使用给空对象提供数据
              origin.push(val)
                // 推入引用对象
              copy.push(_obj[item])
            } else {
              _obj[item] = visitedCopyQueue[index]
              visitedQueue.push(_obj)
            }
          } else if (isTypeOf(val, 'array')) {
            // 数组类型在这里创建了一个空数组
            _obj[item] = []
            origin.push(val)
            copy.push(_obj[item])
          } else if (isTypeOf(val, 'function')) {
            _obj[item] = eval('(' + val.toString() + ')');
          } else {
            _obj[item] = val
          }
        }
        // 将已经处理过的对象数据推入数组 给环状数据使用
        visitedCopyQueue.push(_obj)
      } else if (isTypeOf(items, 'function')) {
        copyObj = eval('(' + items.toString() + ')');
      } else {
        copyObj = obj
      }
    }
  return copyObj
}

测试

/**测试数据 */
// 输入 字符串String
// 预期输出String
let str = 'String'
var strCopy = DFSdeepClone(str)
var strCopy1 = BFSdeepClone(str)
console.log(strCopy, strCopy1) // String String 测试通过
// 输入 数字 -1980
// 预期输出数字 -1980
let num = -1980
var numCopy = DFSdeepClone(num)
var numCopy1 = BFSdeepClone(num)
console.log(numCopy, numCopy1) // -1980 -1980 测试通过
// 输入bool类型
// 预期输出bool类型
let bool = false
var boolCopy = DFSdeepClone(bool)
var boolCopy1 = BFSdeepClone(bool)
console.log(boolCopy, boolCopy1) //false false 测试通过
// 输入 null
// 预期输出 null
let nul = null
var nulCopy = DFSdeepClone(nul)
var nulCopy1 = BFSdeepClone(nul)
console.log(nulCopy, nulCopy1) //null null 测试通过

// 输入undefined
// 预期输出undefined
let und = undefined
var undCopy = DFSdeepClone(und)
var undCopy1 = BFSdeepClone(und)
console.log(undCopy, undCopy1) //undefined undefined 测试通过
  //输入引用类型obj
let obj = {
  a: 1,
  b: () => console.log(1),
  c: {
    d: 3,
    e: 4
  },
  f: [1, 2],
  und: undefined,
  nul: null
}
var objCopy = DFSdeepClone(obj)
var objCopy1 = BFSdeepClone(obj)
console.log(objCopy === objCopy1) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy.c) // 对象类型判断 false 测试通过
console.log(obj.c === objCopy1.c) // 对象类型判断 false 测试通过
console.log(obj.b === objCopy1.b) // 函数类型判断 false 测试通过
console.log(obj.b === objCopy.b) // 函数类型判断 false 测试通过
console.log(obj.f === objCopy.f) // 数组类型判断 false 测试通过
console.log(obj.f === objCopy1.f) // 数组类型判断 false 测试通过
console.log(obj.nul, obj.und) // 输出null,undefined 测试通过

// 输入环状数据
// 预期不爆栈且深度复制
let circleObj = {
  foo: {
    name: function() {
      console.log(1)
    },
    bar: {
      name: 'bar',
      baz: {
        name: 'baz',
        aChild: null //待会让它指向obj.foo
      }
    }
  }
}
circleObj.foo.bar.baz.aChild = circleObj.foo
var circleObjCopy = DFSdeepClone(circleObj)
var circleObjCopy1 = BFSdeepClone(circleObj)
console.log(circleObjCopy, circleObjCopy1) // 测试通过?

ps

关于深浅拷贝的问题博主已经在他的git上有文章说过了,我就不做多的叙述 这两个方法我认为主要区别在于对于深层次以及环状数据,用深度优先遍历递归去做容易爆栈,广度优先遍历我对环状数据进行了处理,已经存在过的对象会存在数组中,下次直接赋值即可,无需继续遍历 如果出现问题欢迎讨论指出

circleObj.foo.bar=1;
执行后发现 circleObjCopy 也被改了

ShopkeeperEgg

ShopkeeperEgg commented on Dec 24, 2024

@ShopkeeperEgg

只考虑基本数据类型和数组和对象。其他数据类型,例如map、set等可以在此基础上拓展

function isSimpleType(e) {
    return typeof e !== "object" || e === null
}

function deepCloneDfs(obj) {
    if (isSimpleType(obj)) return obj;
    const clone = Array.isArray(obj) ? []: {};
    Object.keys(obj).forEach(key => {
        clone[key] = deepCloneDfs(obj[key]);
    });
    return clone;
}

const createBaseObj = obj => Array.isArray(obj) ? []: {};

function deepCloneBfs(obj) {
    if (isSimpleType(obj)) return obj;
    const clone = createBaseObj(obj);
    const queue = [[/*source*/obj ,/*target*/clone]]
    while (queue.length) {
        const [source, target] = queue.shift();
        Object.keys(source).forEach(key => {
            const value = source[key];
            if (isSimpleType(value)) {
                target[key] = value;
            } else {
                const newClone = createBaseObj(value);
                target[key] = newClone;
                queue.push([value, newClone]);
            }
        })
    }
    return clone;
}
brother-forteen

brother-forteen commented on Dec 24, 2024

@brother-forteen
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @Tairraos@whosesmile@coding-nemo@zhw2590582@vivianli-me

        Issue actions

          第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数? · Issue #10 · Advanced-Frontend/Daily-Interview-Question