什么是深浅拷贝?如何实现深浅拷贝?

220 阅读4分钟

什么是深浅拷贝?如何实现深浅拷贝

在聊深浅拷贝之前,先聊一下js数据类型,可以分为值类型(基本类型)引用数据类型(对象类型) JS中的变量都是保存到栈内存中的 基本数据类型 的值直接在栈内存中存储,值与值之间是独立存在,引用数据类型 是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而栈里面保存的是对象的内存地址(对象的引用)

例如下面代码,在内存中大概存这样

let a = 2
let c = 'hello'
let obj1 = {
  name:'obj'
}
let obj2 = obj

@_Q69ASZ_DPY$W_OY(4V{85.png 赋值和深浅拷贝的区别 之前,我一直认为赋值就是浅拷贝,现在发现是错的,赋值对于基本类型来说可以认为是浅拷贝,(基本类型没有深浅拷贝)。

浅拷贝 浅拷贝是指创建一个新的对象,把这个对象的原始属性精确拷贝一份,如果是基本类型就拷贝基本类似的值,如果是引用类型,拷贝的就是内存地址,如果其中一个引用类型改变了值,那么就会影响另一个对象

深拷贝 深拷贝是将一个对象从内存中完成拷贝出来,在内存中开辟一个新的区域存放对象,并且两者不会相互影响

赋值方法

const obj1 = {
  name: "www",
  age: 18
};
let obj2 = obj1;
obj2.age = 20;
console.log(obj1, obj2);
// 可以看到将obj1 赋值 给obj2 修改obj2的值obj1也会变
// { name: 'www' age: 18 } { name:'www' age: 20, }

浅拷贝方法

  • Object.assign() 对象浅拷贝
const obj1 = {
  name: "www",
  age: 18
};
let obj2 = Object.assign({}, obj1);
obj2.age = 20;
console.log(obj1, obj2);
// 可以看到浅拷贝修改之后age的变更并不会相互影响
//{ name: 'www' age: 18 } { name:'www' age: 20, }

看到这里是不是觉得修改新对象的值旧对象不会改变还算浅拷贝吗?如果你也有这样的疑问,那么请往下看

const obj1 = {
  name: "www",
  age: 18,
  hobby: {
    ball: 'basketball',
    track: 'running'
  }
};
let obj2 = Object.assign({}, obj1);
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }

这发现里修改了新对象hobby对象里面的ball值旧对象也会跟着改

其实Object.assign() 拷贝 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝

  • ... 展开运算符 解构浅拷贝
const obj1 = {
  name: "www",
  age: 18,
  hobby: {
    ball: 'basketball',
    track: 'running'
  }
};
let obj2 = { ...obj1 };
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }

该方法和 Object.assign() 效果差不多

  • 手动实现一个浅拷贝
function clone(source) {
  if (typeof source === "object") {
    let target = Array.isArray(source) ? [] : {};
    for (let i in source) {
      if (source.hasOwnProperty(i)) {
        // source.hasOwnProperty(i)意思是_proto_上面的属性我们不拷贝
        target[i] = source[i];
      }
    }
    return target;
  } else {
    return target;
  }
}
const obj1 = {
  name: "www",
  age: 18,
  hobby: {
    ball: 'basketball',
    track: 'running'
  }
};
let obj2 = clone(obj1);
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }

深拷贝方法

  • 简单的方法 JSON.parse(JSON.stringify()) 只适用于固定的数据结构
    这种方法虽然可以实现数组或对象深拷贝,但是有几种情况不能正确进行深拷贝
  1. obj里面有new Date(),深拷贝后,时间会变成字符串形式,而不是时间对象
  2. obj⾥有RegExp、Error对象,则序列化的结果会变成空对象{}
  3. obj⾥有function,undefined,则序列化的结果会把function或 undefined丢失
  4. obj里有NaN、Infinity、-Infinity,则序列化的结果会变成null
  5. JSON.string()只能序列化对象的可枚举的自有属性,如果obj中的对象是由构造函数生成的实例对象,深拷贝后会丢失对象的constructor 3.png
  • lodash 库的_.cloneDeep
let _ = require("lodash");
let obj1 = {
  a: 1,
  b: { d: { e: 1, f: '123' } },
  c: [1, 2, 3],
};
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
  • 手动实现一个简单深拷贝
function deepClone(obj) {
  //定义一个函数判断参数是否是对象
  function isObject(o) {
    return (typeof o == 'object' || typeof o == 'function') && o !== null
  }
  if (!isObject(obj)) { //如果不是对象
    // throw new Error('非对象')
  }
  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  //遍历newObj 此时如果newObj是对象key为对象的key 如果是数字 key 为数组的下标
  Object.keys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[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); //{ a: [ 1, 2, 3 ], b: { c: 2, d: 3 } }
console.log(newObj); //{ a: [ 1, 2, 3 ], b: { c: 1, d: 3 } }