深入理解赋值、浅拷贝、深拷贝

区别

类型和老数据指向同一对象第一层数据为基本类型老数据中包含子对象

赋值

改变会使原数据一同改变

改变会使原数据一同改变

浅拷贝

改变不会使原数据一同改变

改变会使原数据一同改变

深拷贝

改变不会使原数据一同改变

改变不会使原数据一同改变

数据类型

::: warning 基本数据类型 字符串( String )、数字( Number )、布尔( Boolean )、 NullUndefinedSymbol ::: ::: 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