深入理解赋值、浅拷贝、深拷贝
区别
类型
和老数据指向同一对象
第一层数据为基本类型
老数据中包含子对象
赋值
是
改变会使原数据一同改变
改变会使原数据一同改变
浅拷贝
否
改变不会使原数据一同改变
改变会使原数据一同改变
深拷贝
否
改变不会使原数据一同改变
改变不会使原数据一同改变
数据类型
::: warning 基本数据类型 字符串( String
)、数字( Number
)、布尔( Boolean
)、 Null
、 Undefined
、 Symbol
::: ::: warning 引用数据类型 对象( Object
)、数组( Array
)、函数( Function
) :::
基本数据类型直接存储在栈;引用数据类型存储的是该对象的引用地址,通过这个引用值从堆获得真实数据
对比
::: tip 赋值 将对象赋给变量时,赋的起始是对象在栈里的地址;所以变量和原对象指向同一个存储空间,因而改动原对象和变量都会改变存储空间的内容。 :::
let aa = {a: 1, b: {c: 2}}
let bb = aa
bb.a = 2
console.log(bb)
// {a:2, b: {c:2}}
console.log(aa)
// {a:2, b: {c:2}}
bb.b.c = 3
console.log(bb)
// {a:2, b: {c:3}}
console.log(aa)
// {a:2, b: {c:3}}
::: tip 浅拷贝 浅拷贝是对属性值进行拷贝,如果属性是基本类型,拷贝的就是基本类型的值;如果是 Object
, Array
, Function
这类引用数据,拷贝的就是指向存储空间的引用地址;因而改变第一层属性为基本类型的属性值时,原对象数据不会改变;当改变属性为对象的引用类型数据时,原对象会跟着改变。 :::
// 方式一:通过Object.create实现
function shallow (obj) {
if (obj === null || typeof obj !== 'object') {
return obj
} else {
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
}
}
// 方式二:通过Object.assign实现
let aa = {a: 1, b: {c: 2}}
let bb = Object.assign({}, aa)
bb.a = 2
console.log(bb)
// {a:2, b: {c:2}}
console.log(aa)
// {a:1, b: {c:2}}
bb.b.c = 3
console.log(bb)
// {a:2, b: {c:3}}
console.log(aa)
// {a:1, b: {c:3}}
::: tip 深拷贝 在浅拷贝的基础上,递归遍历引用对象属性,直到里面都是基本数据类型为止,再赋值给变量。 :::
// 递归方式
// 不要使用Object.keys遍历:不遍历可枚举的的原型链属性
// 方法一:
function deepCopy (obj) {
let target = {}
if (obj === null || typeof obj !== 'object') {
return obj
}
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (typeof obj[key] === 'object') {
target[key] = deepCopy(obj[key])
} else {
target[key] = obj[key]
}
}
}
return target
}
// 方法二:
function deepCopy (obj, clone = Array.isArray(obj) ? [] : {}) {
if (obj != null && typeof obj === 'object') {
for (const [key, value] of Object.entries(obj)) {
clone[key] = deepCopy(value)
}
} else {
clone = obj
}
return clone
}
let aa = {a: 1, b: {c: 2}}
let bb = deepCopy(aa)
bb.a = 2
console.log(bb)
// {a:2, b: {c:2}}
console.log(aa)
// {a:1, b: {c:2}}
bb.b.c = 3
console.log(bb)
// {a:2, b: {c:3}}
console.log(aa)
// {a:1, b: {c:2}}
::: tip JSON.parse(JSON.stringify())弊端
// 1. Date对象会变成字符串格式
let aa = {a: new Date()}
let bb = JSON.parse(JSON.stringify(aa))
console.log(aa)
// {a: Fri Mar 13 2020 18:26:03 GMT+0800 (中国标准时间)}
console.log(bb)
// {a: "2020-03-13T10:26:03.567Z"}
// 2. 函数会丢弃
let aa = {name: 'MuYi086', width: function () {}}
let bb = JSON.parse(JSON.stringify(aa))
console.log(aa)
// {name: "MuYi086", width: ƒ}
console.log(bb)
// {name: "MuYi086"}
// 3.NaN、Infinity、-Infinity会变成null
let aa = {a: NaN, b: Infinity, c: -Infinity, name: 'MuYi086'}
let bb = JSON.parse(JSON.stringify(aa))
console.log(aa)
// {a: NaN, b: Infinity, c: -Infinity, name: "MuYi086"}
console.log(bb)
// {a: null, b: null, c: null, name: "MuYi086"}
// 4.RegExp、Error对象会变成{}
let aa = {name: 'MuYi086', date: new RegExp('/d+/ig'), day:new Error('gg')}
let bb = JSON.parse(JSON.stringify(aa))
console.log(aa)
// {name: "MuYi086", date: //d+/ig/, day: Error: gg at <anonymous>:1:59}
console.log(bb)
// {name: "MuYi086", date: {}, day: {}}
// 5.丢失对象的constructor
function Person (name) {
this.name = name
}
let zz = new Person('MuYi086')
let yl = {nick: 'og', label: zz}
let copyed = JSON.parse(JSON.stringify(yl))
console.log(yl)
// {nick: "og", label: Person} label: Person {name: "MuYi086"}
console.log(copyed)
// {nick: "og", label: {name: "MuYi086"}}
:::
参考
Last updated
Was this helpful?