H246802 / 30-days-challenge

30天每日打卡
4 stars 0 forks source link

day-18-数组深拷贝 #18

Open H246802 opened 5 years ago

H246802 commented 5 years ago

写一个深拷贝函数 deepCopyArray,实现数组的深拷贝

要求:

思考

let arr = [1, 2, 3];
arr.push(arr)
let arr2 = deepCopyArray(arr)
H246802 commented 5 years ago

第一种

function deepCopyArray(arr) {
    if(!Array.isArray(arr)){
        console.log(`${arr}不是数组`)
        return
    }
    let result = []
    for (let i = 0; i < arr.length; i++){
        if(typeof arr[i] === "number" || typeof arr[i] === "boolean" || typeof arr[i] === "string"){
            result[i] = arr[i]
        } else if(typeof arr[i] === "object" && Array.isArray(arr[i])){
            result[i] = deepCopyArray(arr[i])
        }
    }
    return result
}

第二种

 // 使用JSON 转成 json 类型 string 后再转成对象
// 部分bug:函数类型直接消失、时间类型会变成字符串...
H246802 commented 5 years ago
// 依靠上述代码,我们进行优化,包括对象的深度遍历
// 同时对自身递归时使用闭包代码更高效
function deepCopy(obj) {
  return (function copy(value) {
    if (
      typeof value === "object" &&
      value !== null &&
      !(value instanceof Boolean) &&
      !(value instanceof Date) &&
      !(value instanceof Number) &&
      !(value instanceof RegExp) &&
      !(value instanceof String)
    ) {
      var res;
      if (Array.isArray(value)) {
        res = [];
        value.forEach((ele, i) => {
          res[i] = copy(ele);
        });
      } else {
        res = {};
        Object.keys(value).forEach(function(name) {
          res[name] = copy(value[name]);
        });
      }
      return res
    }
    return value;
  })(obj);
}

关于数组的循环引用

可以参考 cycle.js 原理是将循环的那部分“路径”通过变量缓存起来,如果使用上方的 深拷贝函数,可能会导致栈堆溢出,程序卡死

H246802 commented 5 years ago
// 仿照 cycle.js 写了一个解决循环引用的

if (typeof JSON.decycle !== "function") {
  JSON.decycle = function decyle(object) {
    "use strict";
    //制作一个对象或数组的深层副本,确保最多存在
    // 一个结果结构中对象或数组的实例。该
    // 重复引用(可能正在形成循环)被替换为
    // {“$ ref”:PATH}
    // path 为 $的子属性 $[x] or $[x][y] ...
   // 到最后进行解析
    //生成字符串'[{“$ ref”:“$”}]'。
    // JSONPath用于定位唯一对象。 $表示最高级别
    //对象或数组。
    var objects = new WeakMap(); // 为了提供判断是否是循环引用的证据
    return (function derez(value, path) {
      // derez 提供深度拷贝
      var old_path; // 最早出现的值的路径
      var nu; // 最终copy结果return
      // 判断传入的是否是对象或者数组
      if (
        typeof value === "object" &&
        value !== null &&
        !(value instanceof Boolean) &&
        !(value instanceof Date) &&
        !(value instanceof Number) &&
        !(value instanceof RegExp) &&
        !(value instanceof String)
      ) {
        // 如果是数组或对象,先看看该对象是否在 objects
        // 是否有该属性(这也是闭包的一个理由)
        old_path = objects.get(value);
        if (old_path !== undefined) {
          return { $ref: old_path };
        }
        // 当改对象不存在weakmap属性中时,则设置该值
        objects.set(value, path);

        if (Array.isArray(value)) {
          nu = [];
          value.forEach((element, i) => {
            nu[i] = derez(element, `${path}[${i}]`);
          });
        } else {
          nu = {};
          Object.keys(value).forEach(name => {
            nu[name] = derez(value[name], `${path}[${JSON.stringify(name)}]`);
          });
        }
        return nu;
      }
      return value;
    })(object, "$");
  };
}

if (typeof JSON.retrocycle !== "function") {
  JSON.retrocycle = function retrocycle($) {
    "use strict";
    // 正则判断是否路径符合 $[x][y]... 格式
    // 因为我们是靠 $ 确定的位置
    var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
    // 恢复因为decycle函数减少的对象
    (function rez(value) {
      if (value && typeof value === "object") {
        // 区分数组与对象
        if (Array.isArray(value)) {
          value.forEach((element, i) => {
            if (typeof element === "object" && element !== null) {
              var path = element.$ref;
              if (typeof path === "string" && px.test(path)) {
                // 一步一步借助于引用类型,第一层循环时 value === $
                // 第二步value === $[i]
                // ....
                // 最后不断重复赋值下去,不返回新结果,传进的参数没有变化
                value[i] = eval(path);
              } else {
                rez(element);
              }
            }
          });
        } else {
          Object.keys(value).forEach(function(name) {
            var item = value[name];
            if (typeof item === "object" && item !== null) {
              var path = item.$ref;
              if (typeof path === "string" && px.test(path)) {
                value[name] = eval(path);
              } else {
                rez(item);
              }
            }
          });
        }
      }
    })($);
    return $;
  };
}
H246802 commented 5 years ago

总结一下

JavaScript的深拷贝还有许多的坑,还存在的问题有如何拷贝原型链上的属性?如何拷贝不可枚举属性? 如何拷贝Error对象等等的坑,不过日常生活中使用 JSON.parse(JSON.stringify(obj)) 可以解决大部分问题,如果实在不行,还可以寻找第三方插件,完完全全考虑周全会把问题复杂化。