深拷贝与浅拷贝在其它语言中也经常被提及到,在实际项目开发过程中也常常需要区分当前使用的到底是深拷贝还是浅拷贝,有时候在该使用深拷贝的地方,我们使用了浅拷贝,会导致深藏不露的bug。
在探讨深浅拷贝之前,我们先梳理一下js中的数据类型,js的数据类型分为两类:基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中。
如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是分别存储数据1的地址、数据2的地址和数据3的地址。
js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。
例如,let a = 10 ; b = a , 修改其中一个变量的值,不会影响到另一个变量的值。
浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。
深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
<script>
let arr1 = [1, 42, [3, 4]]
let arr1Copy = arr1.slice()
arr1Copy[0] = 10
arr1Copy[2][0] = 100
console.log(arr1) // [1, 42, [100, 4]]
console.log(arr1Copy) // [10, 42, [100, 4]]
script>
arr1中的元素1是基本数据类型,所以arr1Copy能够改变其值,而不影响arr1的值。
而[3, 4]是引用数据类型,arr1和arr1Copy指向同一块堆内存地址,所以这两个变量中3都变成了100。
let arr2 = ['cat', 'dog', 'pig', {'name': 'xia', 'age': 18}]
let arr2Copy = [].concat(arr2)
arr2Copy[2] = 'big pig'
arr2Copy[3]['name'] = 'aa'
console.log(arr2)
console.log(arr2Copy)
执行结果如下:
类似的还有…扩展运算符、Array.from、Object.assign()方法。
万能转换器 JSON.parse(JSON.stringify(obj))深拷贝已有对象,它可以深拷贝多层级的,不用担心嵌套问题。
let jack = {
name: 'jack'
}
console.log(jack)
console.log(JSON.stringify(jack))
它们在格式上有区别。下图中的第一个是对象,name没有双引号括起来。第二个是json字符串,其中,name用双引号括起来了
let obj = {
name: '静茹秋叶'
}
console.log('obj: ', obj)
console.log('json string: ', JSON.stringify(obj))
let str = JSON.stringify(obj)
console.log('--------------')
console.log(str)
console.log('str to obj: ', JSON.parse(str))
实际项目开发过程中,我使用的这种方式最多,除此之外,还可以使用
两个button-counter共用同一个jack对象,用同一块地址,当其中一个实例改变时,会影响另一个实例的值。(浅拷贝)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue的data选项title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<button-counter>button-counter>
<button-counter>button-counter>
div>
<script>
let jack = {
counter: 0
}
// 子组件
Vue.component('button-counter', {
data() {
// 函数类型
return jack
},
template: ``
})
let vm = new Vue({
el: '#app' // mount到DOM上
})
script>
body>
html>
采用深拷贝,重新创建一块内存。这样,vue的button-counter组件中的counter值互不影响。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue的data选项title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
<div id="app">
<button-counter>button-counter>
<button-counter>button-counter>
div>
<script>
let jack = {
counter: 0
}
// 子组件
Vue.component('button-counter', {
data() {
// 函数类型
return JSON.parse(JSON.stringify(jack))
},
template: ``
})
let vm = new Vue({
el: '#app' // mount到DOM上
})
script>
body>
html>
(例子来源于《Vue.js从入门到项目实战》书籍)
参考资料: