首页 > 编程知识 正文

js深拷贝和浅拷贝,js深拷贝和浅拷贝如何实现

时间:2023-05-05 19:28:22 阅读:230183 作者:3741

基本类型和引用类型

在开始讲解JavaScript的深拷贝和浅拷贝之前,先要认识JavaScript的两种基本数据类型。一种是基本类型(值类型),另外一种是引用类型。其中基本类型包括undefined、null、number、string和boolean,这几种类型在内存中都有固定的大小和空间。引用类型包括object,这种值的大小不固定,可以动态添加属性和方法,而基本类型则不可以。

基本类型的值保存在内存中的栈中,而引用数据类型的值保存在内存中的堆中,在栈内存中保存着指向堆内存的指针。如果此时你对栈或者堆这样的数据结构不太了解的话,没有关系,不会影响你对后面内容的理解。只要先记住栈和堆着两个名词就可以。

如图所示,这是一个引用数据类型,也就是对象(object)在内存中的保存方式。比如我们定义方式如下所示:

let Foo = {words: "haha"}

对象的内容保存在堆中,而对象的引用,也就是这里的Foo,保存在栈中,并指向对应的堆。

我们明白了这样的道理之后,就会清楚对于基本数据类型的值的复制,是对栈内存中的值直接进行复制,所以复制的就是值本身,相当于复制了一个副本。会在栈中开辟一块全新的内存。所以修改一个变量的值不会影响另外一个变量的值。所以对于基本数据类型而言,没有浅拷贝和深拷贝之分,或者说直接就是深拷贝。


引用类型浅拷贝

对于引用类型进行复制,如下代码所示,复制的并不是堆内存中的数据,而是指向堆中的栈内存的指针。如上面那个图所示,原先在标记为a的栈内存中有一个Foo变量,指向对应的堆内存中的数据,现在通过复制这个指针,在堆内存中新开辟一个内存空间,我们标记为c,其指向的和a是同样的堆内存数据。所以修改a和c都会导致堆内存中的数据发生改变。

let Foo = { a: 3, b: 4}let newFoo = FoonewFoo.a = 5

之后输出的结果是:


由于Foo和NewFoo都指向同样的对象,所以改变NewFoo中的数据,Foo中的数据同样也会发生改变。那如何去改变这一点呢?让Foo和NewFoo中的数据不同,改变一个不会影响另外一个?我们先谈一下浅拷贝的解决办法。

浅拷贝可以使用Object.assign()来实现。我们还是举上面的例子:

let Foo = { a: 3, b: 4}// let newFoo = FooObject.assign(newFoo, Foo)newFoo.a = 5

这时候输出的结果是:

可以看出,Foo的结果并没有发生改变,达到了拷贝的目的。但这为什么又称之为浅拷贝呢?我们来看下面一个例子。

let Foo = { a: { c:1 }, b: 4}// let newFoo = FooObject.assign(newFoo, Foo)newFoo.a.c=2

这时候输出结果是:

可以看出,此时c的结果发生了改变,已经变成2了。这就是浅拷贝的意思。只拷贝了最浅层的对象,当对象里面嵌套的对象发生改变时,其内部值也会发生改变,没有达到完全隔离的效果,只实现了浅层的拷贝。下面我们一起来看一下如何实现深层拷贝。


引用类型深拷贝

实现深层拷贝方式有两种,一种是通过JSON的方式,另外一种是通过类型判断和递归的方式。我们分别来实现一下。

JSON方式

JSON有两个方法JSON.parse()和JSON.stringify(),前者可以将JSON字符串转换成JavaScript对象,后者可以将JavaScript对象转换成JSON字符串。

所以解决思路就是先将引用类型数据变成JSON,再将其从JSON变回来,这样经过这样一层转换,就会要求计算机从内存中重新开辟一块新的内存空间。Foo和newFoo之间就不会发生任何联系了。我们通过代码来去具体看一下。

let Foo = { a: { c:1 }, b: 4}let str = JSON.stringify(Foo)let newFoo = JSON.parse(str)newFoo.a.c = 5

此时Foo输出的结果是:

可以看到c并没有发生改变,实现了深拷贝。

通过递归的方式实现

之前JSON方式实现深拷贝就是靠着返回一个独立的对象来实现深拷贝,受此启发,如果要是我们自己来实现深拷贝的话,我们可以将原来的数据完全拷贝之后,然后返回一个独立的新对象,这样的话,也可以实现深拷贝。

如果要实现完全遍历的话,就要考虑Object的不同具体数据类型了,虽然{}和[]都是Object类型,但显然生成的对象是不一样的,当然,可能Object还可能有别的数据类型的样子,这里我们就以这两个为例,来去自己动手实现深拷贝。

那么首先需要写一个数据类型判断的函数:

let checkType = data => { return Object.prototype.toString.call(data).slice(8, -1)}

这里我们使用Object的toString方法来去判断data的数据类型,后面的slice是返回值结果进行一定的截取,保留我们想要的结果。

下面定义深拷贝函数:

let deepClone = target => { let targetType = checkType(target) let result // 初始化操作 if (targetType === 'Object') { result = {} } else if (targetType === 'Array') { result = [] } else { // 都不是的话证明是基本数据类型,基本数据 // 类型只会有一个值,所以直接返回这个值就可以了 return target } // target不是基本类型,进入遍历 for (let i in target) { let value = target[i] let valueType = checkType(value) if (valueType === 'Object' || valueType === 'Array') { result[i] = deepClone(value) // 递归 } else { // 是基本类型直接赋值 result[i] = value } } return result}

这样我们实现了自己定义的深拷贝。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。