欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

javascript 对象的深拷贝与浅拷贝

程序员文章站 2022-07-02 19:28:26
今天我们来谈一谈对象的深拷贝和浅拷贝吧我们都知道,js数据类型主要分为两大类 基础数据类型和引用(复杂)数据类型。基础数据类型存在于栈内存中,当被拷贝时,会创建一个完全相等的变量而引用数据类型存在于堆中,存储的是一个内存空间,而赋值给变量的,仅仅是这个内存空间的一个引用而已。而就会出现一个问题,当我们将一个对象赋值给另一个变量时,赋值的是对象的引用,必然导致两个变量都指向同一个内存空间,其中一个改变时,必然会影响到另一个。浅拷贝和深拷贝的理解什么是浅拷贝简单理解就是自己创建一个新的对....

今天我们来谈一谈对象的深拷贝和浅拷贝吧

我们都知道,js数据类型主要分为两大类 基础数据类型和引用(复杂)数据类型。

  • 基础数据类型存在于栈内存中,当被拷贝时,会创建一个完全相等的变量
  • 而引用数据类型存在于堆中,存储的是一个内存空间,而赋值给变量的,仅仅是这个内存空间的一个引用而已。而就会出现一个问题,当我们将一个对象赋值给另一个变量时,赋值的是对象的引用,必然导致两个变量都指向同一个内存空间,其中一个改变时,必然会影响到另一个。

浅拷贝和深拷贝的理解

什么是浅拷贝

简单理解就是
自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

可以实现浅拷贝的方法
1、Object.assign()

  • object.assign 是es6新增的一个方法
  • 用于合并多个对象到目标对象中
  • 第一个参数是目标对象,后面的参数是源对象
var obj = {}
var source1 = {
	name: '张三',
	age: 18,
	like: {
		music: '老鼠爱大米'
	}
}
Object.assign(obj, source1)
console.log(obj)
source1.like.music= "沙漠骆驼"
console.log(obj)
// 输出
{ name: '张三', age: 18, like: { music: '老鼠爱大米' } }
{ name: '张三', age: 18, like: { music: '沙漠骆驼' } }

从上面可以看出,修改source1的like属性下的music属性值时,会对obj产生影响

2、扩展运算符

  • 扩展运算符可以展开对象或者数组中的选项 obj = { …obj2 }
var obj = {}
var source1 = {
	name: '张三',
	age: 18,
	like: {
		music: '老鼠爱大米'
	}
}
obj = { ...source1 }
console.log(obj)
source1.like.music= "沙漠骆驼"
console.log(obj)
// 输出
{ name: '张三', age: 18, like: { music: '老鼠爱大米' } }
{ name: '张三', age: 18, like: { music: '沙漠骆驼' } }

可以发现,和Object.assign() 一样的结果

同时,使用这两种方式还需要注意

  • 它不会拷贝对象的继承属性
  • 它只能拷贝可枚举的属性
  • 它可以拷贝 Symbol 类型的属性
var obj = {
	name: '张三',
	age: 18,
	sym: Symbol(123)
}
Object.defineProperty(obj, 'aaa', {
    value: '这是一个不可枚举的属性',
    enumerable: false // 设置为false代表此属性不可枚举
})
var obj2 = { ...obj }
console.log(obj2)
// 输出
{ name: '张三', age: 18, sym: Symbol(123) }

从上面可以看出,symbol属性被拷贝了,但是属性aaa并没有被拷贝

3、数组拷贝方法
concat ()
使用方法我就不多说了,不懂得看我上一篇文章吧

var arr = [{name: '张三'}, 2, {age: 18}]
var brr = arr.concat()
console.log(brr)
arr[0].name = '李四'
console.log(brr)
// 输出
[ { name: '张三' }, 2, { age: 18 } ]
[ { name: '李四' }, 2, { age: 18 } ]

可以看出,concat()也是浅拷贝

slice()
使用方法见上一篇文章

var arr = [{name: '张三'}, 2, {age: 18}]
var brr = arr.slice()
console.log(brr)
arr[0].name = '李四'
console.log(brr)
// 输出
[ { name: '张三' }, 2, { age: 18 } ]
[ { name: '李四' }, 2, { age: 18 } ]

和concat一样,属于浅拷贝

扩展运算符
数组扩展运算符和对象得一模一样,我就不另外说了

深拷贝

我们可能大多时候最常用到的深拷贝就是JSON.stringify() 了。相信大家都用过
原理就是先将对象转为json字符串,再转换为对象。

var obj = {
    name: '张三',
    like: {
        music: '老鼠爱大米',
        color: 'red',
        animate: {
            name: '狗'
        }
    }
}
var obj2 = JSON.parse(JSON.stringify(obj))
obj.like.music = '沙漠骆驼'
obj.like.animate.name = '熊猫'
console.log(obj)
console.log(obj2)
// 输出
{
  name: '张三',
  like: { music: '沙漠骆驼', color: 'red', animate: { name: '熊猫' } }
}
{
  name: '张三',
  like: { music: '老鼠爱大米', color: 'red', animate: { name: '狗' } }
}

可以看出,更改obj 不会影响到拷贝出来的obj2

但是这种方法也存在很多问题

  1. 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失,导致无法拷贝到
  2. 拷贝 Date 引用类型会变成字符串
  3. 无法拷贝不可枚举的属性
  4. 拷贝 RegExp 引用类型会变成空对象
  5. 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null
var obj = {
    base: {
        name: '张三'
    }
}
obj.date = new Date(0)
obj.un = undefined
obj.nu = null
obj.infinity = Infinity
obj.nan = NaN
obj.sym = Symbol(123)
obj.reg = /^123/
Object.defineProperty(obj, 'aaa', {
    value: '这是一个不可枚举的属性',
    enumerable: false // 设为false代表不可枚举
})
var obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj)
console.log(obj2)

// 输出
{
  base: { name: '张三' },
  date: 1970-01-01T00:00:00.000Z,
  un: undefined,
  nu: null,
  infinity: Infinity,
  nan: NaN,
  sym: Symbol(123),
  reg: /^123/
}
{
  base: { name: '张三' },
  date: '1970-01-01T00:00:00.000Z',
  nu: null,
  infinity: null,
  nan: null,
  reg: {}
}

从上面可以看出

  • Date对象变成字符串了
  • infinity 和 NaN 变成null了
  • un属性的值是undefined,并没有被拷贝到,因为这个属性在序列号时消失了,sym属性也是一样消失了
  • RegExp引用类型变成空对象了
  • aaa这个不可枚举的属性也没有被拷贝到

那么,如何实现一个完整的深拷贝方法呢,那就要看我们该如何一一来解决以上问题了

  • 拷贝引用类型属性时,我们可以通过递归来循环拷贝
  • 如果属性是简单数据类型,包括undefined,infinity 和 NaN, 则直接递归赋值
  • 拷贝Date对象时,我们可以直接重新生成一个新的Date实例返回
  • 拷贝RegExp类型时,也是一样,直接生成一个新的RegExp实例返回
  • 拷贝Symbol属性和不可枚举的属性时,使用Reflect.ownKeys (什么是Reflect.ownKeys)方法
function isObject (obj) {
    if ((typeof obj === 'object' || typeof obj === 'function') && (obj !== null)) {
        return true
    }
}
function deepClone (obj) {
    if (obj.constructor === Date) {
        return new Date(obj) // 返回一个新实例
    }
    
    if (obj.constructor === RegExp) {
        return new RegExp(obj) // 返回一个新实例
    }

    let allDesc = Object.getOwnPropertyDescriptors(obj)
    // 遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

    for (let key of Reflect.ownKeys(obj)) { 
        cloneObj[key] = (isObject(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}
var obj = {
    base: {
        name: '张三'
    }
}
obj.date = new Date(0)
obj.un = undefined
obj.nu = null
obj.infinity = Infinity
obj.nan = NaN
obj.sym = Symbol(123)
obj.reg = /^123/
Object.defineProperty(obj, 'aaa', {
    value: '这是一个不可枚举的属性',
    enumerable: false // 设为false代表不可枚举
})

var obj2 = deepClone(obj)
console.log(obj)
console.log(obj2)

// 输出
{
  base: { name: '张三' },
  date: 1970-01-01T00:00:00.000Z,
  un: undefined,
  nu: null,
  infinity: Infinity,
  nan: NaN,
  sym: Symbol(123),
  reg: /^123/
}
{
  base: { name: '张三' },
  date: 1970-01-01T00:00:00.000Z,
  un: undefined,
  nu: null,
  infinity: Infinity,
  nan: NaN,
  sym: Symbol(123),
  reg: /^123/
}

从结果可以看出,我们实现的深拷贝函数确实实现了对象的一个深拷贝功能。

深拷贝与浅拷贝就写到这了。有问题欢迎指出

本文地址:https://blog.****.net/weixin_42707287/article/details/112576039

相关标签: javascript js