JS实现深拷贝和浅拷贝


JS 实现深拷贝和浅拷贝

浅拷贝

浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;

方式一

通过 Object.assign 来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。

const a = { name: "javascript", age: 23 };
const b = Object.assign({}, a);
a.age = 86;
console.log("---a---", a); // ---a--- { name: 'javascript', age: 86 }
console.log("---b---", b); // ---b--- { name: 'javascript', age: 23 }
方式二

使用展开运算符…来实现浅拷贝

const a = { name: "javascript", age: 23 };
const b = { ...a };
a.age = 86;
console.log("---a---", a); // ---a--- { name: 'javascript', age: 86 }
console.log("---b---", b); // ---b--- { name: 'javascript', age: 23 }

但是上面两种方法在对象的属性值也为对象的时候就不适用了,这个时候就需要进行深拷贝。

const a = {
  name: "javascript",
  age: 23,
  jobs: {
    first: "student",
  },
};
const b = { ...a };
a.jobs.first = "FE";
console.log("---b.jobs.first---", b.jobs.first); // FE

深拷贝

深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。

方式一

使用 JSON.parse(JSON.stringify())的方式来实现深拷贝。

const a = {
  name: "javascript",
  age: 23,
  jobs: {
    first: "student",
  },
};
// const b = { ...a };
const b = JSON.parse(JSON.stringify(a));
a.jobs.first = "FE";
console.log("---a.jobs.first---", a.jobs.first); // FE
console.log("---b.jobs.first---", b.jobs.first); // student

该方法的局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
const a = {
  name: undefined,
  age: Symbol(23),
  jobs: function () {},
  hobby: "play game",
};
const b = JSON.parse(JSON.stringify(a));
console.log("---b---", b); // ---b--- { hobby: 'play game' }
方式二

如果所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel。

function structuralClone(obj) {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = (ev) => resolve(ev.data);
    port1.postMessage(obj);
  });
}
var obj = {
  a: 1,
  b: {
    c: 2,
  },
};
obj.b.d = obj.b;
// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
  const clone = await structuralClone(obj);
  console.log(clone);
};
test();
方式三

自定义实现一个深拷贝,但是其实实现一个深拷贝是很困难的,需要考虑多种边界情况,比如原型链如何处理、DOM 如何处理等等,所以只能实现一个简易版的深拷贝,此外还可以使用lodash 的深拷贝函数

// 不可以拷贝函数
function deepClone(obj) {
  function isObject(obj) {
    return (
      (typeof obj === "object" || typeof obj === "function") && obj !== null
    );
  }
  if (!isObject(obj)) return obj;
  const isArray = Array.isArray(obj);
  let newObj = isArray ? [...obj] : { ...obj };
  // 用递归实现深拷贝
  Reflect.ownKeys(newObj).forEach((key) => {
    newObj[key] = isObject(newObj[key]) ? deepClone(newObj[key]) : newObj[key];
  });
  return newObj;
}
let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3,
  },
};
let newObj = deepClone(obj);
newObj.b.c = 1;
console.log(obj.b.c); // 2
深拷贝(尤雨溪版)
function find(list, f) {
  return list.filter(f)[0];
}

function deepCopy(obj, cache = []) {
  // just return if obj is immutable value
  // 判断类型是否为原始类型,如果是,无需拷贝,直接返回
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // if obj is hit, it is in circular structure
  // 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
  const hit = find(cache, (c) => c.original === obj);
  if (hit) {
    return hit.copy;
  }
  // 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
  const copy = Array.isArray(obj) ? [] : {};
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy,
  });
  // 对引用类型递归拷贝直到属性为原始类型
  Object.keys(obj).forEach((key) => (copy[key] = deepCopy(obj[key], cache)));

  return copy;
}

参考


文章作者: CassielLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CassielLee !
评论
  目录