Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

day-18-数组深拷贝 #18

Open
H246802 opened this issue Dec 12, 2018 · 4 comments
Open

day-18-数组深拷贝 #18

H246802 opened this issue Dec 12, 2018 · 4 comments

Comments

@H246802
Copy link
Owner

H246802 commented Dec 12, 2018

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

要求:

  • 数组可嵌套
  • 数组元素只能是 Number, Boolean, String, Array 类型之一

思考

  • 当存在循环引用时该如何解决
let arr = [1, 2, 3];
arr.push(arr)
let arr2 = deepCopyArray(arr)
@H246802
Copy link
Owner Author

H246802 commented Dec 12, 2018

第一种

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
Copy link
Owner Author

H246802 commented Dec 12, 2018

// 依靠上述代码,我们进行优化,包括对象的深度遍历
// 同时对自身递归时使用闭包代码更高效
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
Copy link
Owner Author

H246802 commented Dec 14, 2018

// 仿照 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
Copy link
Owner Author

H246802 commented Dec 14, 2018

总结一下

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant