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

JavaScript 深浅拷贝 #21

Open
juzhiyuan opened this issue Jul 2, 2018 · 0 comments
Open

JavaScript 深浅拷贝 #21

juzhiyuan opened this issue Jul 2, 2018 · 0 comments

Comments

@juzhiyuan
Copy link
Owner

juzhiyuan commented Jul 2, 2018

改自 https://scotch.io/bar-talk/copying-objects-in-javascript

一个对象就是一组属性值的集合,一组属性值关联着一对 Key 与 Value。在 JavaScript 中,几乎所有的对象都来自于 Object,它在原型链的顶端

介绍

分配符(=)并不会真正拷贝一个对象,它仅仅拷贝了对象的 引用

let obj = {
  a: 1,
  b: 2
}

let copy = obj

obj.a = 5
console.log(copy.a) // 输出 5

变量copy引用该对象,因此对象{a: 1, b: 2}标明:有两种方式(obj或者copy)操作我,不管操作哪一个都会改变我

幼稚的拷贝对象方式

循环原对象,拷贝它的每一个属性:

function copy(mainObj) {
  let objCopy = {}
  let key

  for (key in mainObj) {
    objCopy[key] = mainObj[key]
  }

  return objCopy
}

const mainObj = {
  a: 2,
  b:5,
  c: {
    x: 7,
    y: 4
  }
}

console.log(copy(mainObj))

存在的问题

  • 对象objCopy存在一个不同于对象mainObj原型的Object.prototype,这不是我们想要得到的。我们希望拷贝一个完整的原对象
  • 属性描述符没有被拷贝,一个值为falsewritable描述符,在对象objCopy中将为true
  • 上方代码仅仅拷贝对象mainObj可枚举属性
  • 如果原对象中,一个属性对应的值为一个对象,那么它会被拷贝引用而不是

浅拷贝

在没有任何引用的情况下复制源顶级属性时,对象被称为浅层复制,并且存在一个源属性,其值为对象并作为引用复制。如果源值是对象的引用,则它仅将该引用值复制到目标对象。

浅拷贝将复制顶级属性,但嵌套对象在原始(源)和副本(目标)之间共享。

使用 Object.assign() 方法

Object.assign()方法用来拷贝一个或多个源对象自身所拥有的(不包含原型链)且可枚举属性到目标对象

let obj = {
  a: 1,
  b: 2
}

let objCopy = Object.assign({}, obj)
console.log(objCopy) // 输出:{a: 1, b: 2}

objCopy.b = 89
console.log(objCopy) // 输出:{a: 1, b: 89}
console.log(obj) // 输出:{a: 1, b: 2}

上方代码中,我们实现了对obj的拷贝,当改变了对象objCopyb属性的值为89并打印它后,发现本次修改只针对于对象objCopy,没有改变对象obj。这表明我们成功的从原对象生成了一个拷贝对象,并且没有拷贝其引用。

别着急,尽管看上去一切都没问题,但是还记得我们说过的浅拷贝吗?看下面的例子:

let obj = {
  a: 1,
  b: {
    c: 2
  }
}
let newObj = Object.assign({}, obj)
console.log(newObj) // 输出:{a: 1, b: {c: 2}}

obj.a = 10
console.log(obj) // 输出:{a: 10, b: {c: 2}}
console.log(newObj) // 输出:{a: 1, {b: {c: 2}}}

newObj.a = 20
console.log(obj); // { a: 10, b: { c: 2} }
console.log(newObj); // { a: 20, b: { c: 2} }

newObj.b.c = 30
console.log(obj); // { a: 10, b: { c: 30} }
console.log(newObj); // { a: 20, b: { c: 30} }

当执行obj.b.c = 30后,对象obj与对象newObj的对象属性b的属性c值均发生了改变,这是因为Object.assign方法仅实现了浅拷贝newObj.bobj.b都引用了同一个对象,而不是单独对值进行复制。任何对引用值得改变都会影响引用它的其它对象。

注意:在原型链上的属性或者不可枚举的属性都不会被拷贝

let someObj = {
  a: 2
}

let obj = Object.create(someObj, {
  b: {
    value: 2
  },
  c: {
    value: 3,
    enumerable: true
  }
})

let objCopy = Object.assign({}, obj)
console.log(objCopy)
  • 对象someObj对象obj原型链上,它不会被拷贝
  • 属性 b不可枚举属性
  • 属性 c存在可枚举属性描述符,因此它可以被拷贝

深拷贝

深拷贝将复制它遇到的每个对象,副本和原始对象不会共享任何内容,因此它将是原始副本。下面我们解决下使用Object.assign()进行浅拷贝遇到的问题:

使用 JSON.parse(JSON.stringify(object))

这解决了我们之前遇到的问题。 现在newObj.b有副本而不是引用! 这是深拷贝对象的一种方法:

let obj = {
  a: 1,
  b: {
    c: 2
  }
}

let newObj = JSON.parse(JSON.stringify(obj))

obj.b.c = 20
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }

但是上述方法不适用于拷贝用户定义的对象内方法

拷贝对象方法

方法是作为函数对象的属性,我们尝试拷贝对象内方法:

let obj = {
  name: 'jjzhiyuan',
  exec: function() {
    return true
  }
}

let method1 = Object.assign({}, obj)
let method2 = JSON.parse(JSON.stringify(obj))

console.log(method1)

// 输出

/*
* {
*   exec: function exec() {
*     return true
*   },
*   name: "jjzhiyuan"
* }
*/

console.log(method2)

// 输出

/*
* {
*   name: "jjzhiyuan"
* }
*/

结果显示:Object.assign()方法可以用于拷贝对象内方法,JSON.parse(JSON.stringify(obj))无法拷贝

拷贝嵌套对象

指存在引用自身属性的对象,我们尝试实现对其的拷贝:

使用 JSON.parse(JSON.stringify(object))

let obj = { 
  a: 'a',
  b: { 
    c: 'c',
    d: 'd',
  },
}

obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;

let newObj = JSON.parse(JSON.stringify(obj));

console.log(newObj); 

输出如下:
image
因此 JSON.parse(JSON.stringify(obj))不能用于拷贝嵌套对象

使用 Object.assign()

let obj = { 
  a: 'a',
  b: { 
    c: 'c',
    d: 'd',
  },
}

obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;

let newObj2 = Object.assign({}, obj);

console.log(newObj2); 

输出如下:
image
Object.assign()可以实现对嵌套对象的浅拷贝

使用 扩展运算符(...)

const array = [
  "a",
  "c",
  "d", {
    four: 4
  },
];
const newArray = [...array];
console.log(newArray);
// 输出
// ["a", "c", "d", { four: 4 }]

对象初始值设定项中的Spread属性将源对象中的自身可枚举属性复制到目标对象上:

let obj = {
  one: 1,
  two: 2,
}

let newObj = { ...obj }

// { one: 1, two: 2 }
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