一行代码

JSON.parse(JSON.stringify(data))

注意点

  • 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
  • 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
  • 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
  • JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
  • 如果对象中存在循环引用的情况也无法正确实现深拷贝

    数组

  1. function(arr){
  2. return Arrary.prototype.slice.call(arr)
  3. }

考虑到数组

  1. function deepClone(source){
  2. let type = Object.prototype.toString.call(source)
  3. if(type ==='[object Arrary]' || type ==='[object Object]'){
  4. let target = type ==='[object Arrary]'?[]:{};
  5. for(var k in source){
  6. if(source.hasOwnProperty(k)){ // 继承自身属性而不继承原型链上面的
  7. target[k] = deepClone(source[k])
  8. }
  9. }
  10. }else{
  11. return source
  12. }
  13. }

循环引用

使用数组

  1. function deepClone(source,uniqueList=[]){ //设置默认值
  2. let type = Object.prototype.toString.call(source)
  3. if(type ==='[object Arrary]' || type ==='[object Object]'){
  4. let target = type ==='[object Arrary]'?[]:{};
  5. let flag = uniqueList.find(item=> item['source'] === source)
  6. if(flag){
  7. return flag['target']
  8. }
  9. uniqueList.push({
  10. source,
  11. target
  12. })
  13. for(var k in source){
  14. if(source.hasOwnProperty(k)){ // 继承自身属性而不继承原型链上面的
  15. target[k] = deepClone(source[k])
  16. }
  17. }
  18. }else{
  19. return source
  20. }
  21. }

什么是循环引用

  1. var a = {
  2. name: "muyiy",
  3. book: {
  4. title: "You Don't Know JS",
  5. price: "45"
  6. },
  7. a1: undefined,
  8. a2: null,
  9. a3: 123
  10. };
  11. a.text = a //当出现这种情况时就产生了循环引用

原理

  1. let flag = uniqueList.find(item=> item['source'] === source)
  2. if(flag){
  3. return flag['target']
  4. }
  5. uniqueList.push({
  6. source, // 父元素
  7. target // 拷贝返回的子元素,应为是对象所以值会随着调用而改变
  8. })
  • 定义一个状态存储的uniqueList栈,存储每次clone的父元素,
  • 因为正常情况下父元素调用完之后如果子元素如果为对象的话,则将子元素作为下一个拷贝的子元素;

也就是说父元素不会调用两次,

  • 如果等于的话也就产生了循环调用,这时直接返回存储的子元素拷贝而不继续循环

    使用哈希表

  1. function deepClone(source,hash = new WeakMap()){
  2. let type = Object.prototype.toString.call(source)
  3. if(type ==='[object Arrary]' || type ==='[object Object]'){
  4. let target = Arrary.isArray(source)?[]:{}
  5. // 存在相同的话则表明循环调用了
  6. if(hash.has(source)) return hash.get(source)
  7. has.set(source,target)
  8. for(var key in source){
  9. if(Object.prototype.hasOwnProperty.call(source,key)){ // 属性是否可被枚举
  10. target[key] = cloneDeep3(source[key],hash)
  11. }
  12. }
  13. }else{
  14. return source
  15. }
  16. }

解决栈爆破

当嵌套的层数太多时就会发生内存泄露,

方法一使用setTimeout分片

递归爆栈可以使用setTimeout分片,同步执行分片推入队列中,而且不影响后面的代码执行,
// 在之前循环引用-数组模式中改变代码
//target[i] = cloneDeep2(source[i],uniqueList) 变为

  1. target[key] = await new Promise(resolve => {
  2. setTimeout(async() => {
  3. resolve(await cloneDeep2(source[key]))
  4. }, 0);
  5. });

此时的数据调用方法变为

  1. var data = cloneDeep(a);
  2. data.then(v=>{
  3. console.log(v);
  4. }
  5. );

使用栈结构

  1. ar obj1 = [1, 2, 3, { a: 1, b: 2, c: [1, 2, 3] }];
  2. var deepClone = function(obj) {
  3. var root = Array.isArray(obj) ? [] : {};
  4. var nodeList = [
  5. {
  6. parent: root,
  7. key: undefined,
  8. data: obj,
  9. },
  10. ];
  11. while (nodeList.length) {
  12. let node = nodeList.pop(),
  13. parent = node.parent,
  14. k = node.key,
  15. data = node.data;
  16. let result = parent;
  17. if (typeof k !== 'undefined') {
  18. result = parent[k] = Array.isArray(data)? [] : {};
  19. }
  20. for (let key in data) {
  21. if (data.hasOwnProperty(key)) {
  22. if (typeof data[key] === 'object') {
  23. nodeList.push({
  24. parent: result,
  25. key,
  26. data: data[key],
  27. });
  28. } else {
  29. result[key] = data[key];
  30. }
  31. }
  32. }
  33. }
  34. return root;
  35. };