面试题被问到再也不慌,深究JavaScript中的深拷贝与浅拷贝

2021-01-29 16:19:33 浏览数 (1)

浅显易懂的介绍Javascript中的深拷贝和浅拷贝

  • 引言
  • 正文
    • 一、简单介绍
    • 二、浅拷贝
      • Object.assign
    • 二、深拷贝
  • 结束语

引言

先点赞,再看博客,顺手可以点个关注。 微信公众号搜索【Lpyexplore的编程小屋】,关注我,带你在python爬虫的过程中学习前端

JavaScript中的深拷贝和浅拷贝是前端面试中频繁被问到的一道题, 于是我也自己去查阅了一些资料, 然后动手敲了代码测试了一下。那么我就用我的理解,给大家来讲解一下这个深拷贝和浅拷贝吧。

正文

一、简单介绍

JavaScript中的变量一共有两种类型的值: 基本类型值引用类型值

  • 基本类型值: 是指StringNumberundefinedNullboolean等,他们在内存中都是存储在栈中的 , 即直接访问该变量就可以得到存储在栈中的该变量的值。 若将一个变量的值赋值给另一个变量, 则这两个变量在内存中是独立的, 修改其中一个变量的值, 不会影响另一个变量。

我们来看一下图片介绍:

  1. 创建了第一个变量 a , 并赋值一个 ’ 哈哈 ’ 给它,则在内存中的表现形式为:
  1. 然后又创建了一个变量 b ,将 变量 a赋值给b,则此时在内存中的表现形式为:

可以看到在栈中, b独立占有一个位置,并且拥有自己的值,为 ’ 哈哈 ’

  1. 然后此时我们将变量a 的值改为 ’ 嘿嘿 ’ , 此时在内存中的情况是:

可以看到, 只有变量a的值变为了 ’ 嘿嘿 ’ , 这是因为 b 已经独立存在于栈中了,他不会受到 变量a 的影响。

  • 引用类型值:是指ObjectArrayFunction等,他们在内存中是存在于栈与堆中, 即我们要访问到引用类型值的话, 需要先访问到该变量在栈中的地址(指向堆中的值), 然后再通过这个地址,访问到存放在堆中的数据。
  1. 我们先创建一个变量 a ,并赋值一个{name: '张三'},此时在内存中的情况就是:

我们可以看到, 给变量a 赋值了一个 Object数据类型后,在内存中是,Object存储在堆中,而栈中存储的是该变量名和对应堆中的Object的地址

  1. 然后我们再创建一个变量b,将变量a 赋值给变量b,此时内存中的情况是:

我们可以看到, 当把变量a 赋值给变量b 的时候, 只是在栈中创建了一个变量b,然后将 变量a 存储在栈中的地址1 给了变量b , 所以此时变量a 和 变量b都指向堆中的同一个值.

  1. 最后我们 改变一下变量a 中的值, 即a.name = ' 李四 ', 此时内存中的情况是:

我们可以看到,因为变量a 和 变量b 都指向堆中的同一个值,所以当通过变量a 改变该值时, 变量a 和 变量b 对应的值也都都会跟着变。

那么如何使变量b 独立存在而不受变量a 的影响呢?接下来我们看看浅拷贝和深拷贝

二、浅拷贝

首先说一下, 深拷贝和浅拷贝主要是针对像Object 和 Array 这两种比较复杂的对象的。

那么什么是浅拷贝呢? 简单来说,就是一个变量赋值给另一个变量,其中一个变量的值改变,两个变量的值都变了,这就叫做浅拷贝。

我们来看一个最简单的浅拷贝例子:

代码语言:javascript复制
let a = {name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞']}
let b = {}
for(let i in a) {
	b[i] = a[i]
}
a.name = '李四'
a.like[0] = '睡觉'
console.log(a)
console.log(b)
// { name: '李四', age: 19, like: [ '睡觉', '唱歌', '跳舞' ] }
// { name: '张三', age: 19, like: [ '睡觉', '唱歌', '跳舞' ] }

我们可以看到,变量b 通过一个一个获取变量a 中的元素, 已经独立存在, 改变了变量a 的 name,变量b 的 name却没有跟着改变。但是又能发现,变量a 改变了 like 值, 变量b 也随之改变了,好像并没有独立存在。 其实这是因为变量b 在获取变量a 的每一个元素时, 遇到 like , 发现它是我们上边说到的引用类型值, 所以变量b 就获得了一个地址,指向了堆中的值, 所以变量b 中的 like 仍然不是独立存在的。

这就是一个浅拷贝的例子。总结来说就是, 浅拷贝只是将外层的值独立了出来, 例如这个例子中的 name 、 age , 都不会随着变量a 的改变而改变了; 但是由于对象结构复杂,内部的引用类型值没有独立出来, 例如这个例子中的 like , 变量b 获取到以后只是获取到了一个地址, 它跟变量a 的 like 指向同一个对象, 所以变量b 的 like 仍然会随着 变量a 的 like 改变而改变。

Object.assign

ES6语法中也给我们提供了一个浅拷贝的方法Object.assign(target, sources)

  • target:拷贝的目标
  • sources: 被拷贝的对象
代码语言:javascript复制
let a = {name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞']}
let b = {}
Object.assign(b, a)
a.name = '李四'
a.like[0] = '睡觉'
console.log(a)
console.log(b)
// { name: '李四', age: 19, like: [ '睡觉', '唱歌', '跳舞' ] }
// { name: '张三', age: 19, like: [ '睡觉', '唱歌', '跳舞' ] }

二、深拷贝

那么浅拷贝, 是拷贝后,新拷贝的对象内部仍然有一部分数据会随着源对象的变化而变化, 那么深拷贝就是,拷贝后, 新拷贝的对象内部所有数据都是独立存在的,不会随着源对象的改变而改变。

深拷贝的话一共有两种方式: 递归拷贝利用JSON函数深拷贝

  • 递归拷贝

实现原理: 对变量中的每个元素进行获取,若遇到基本类型值,直接获取;若遇到引用类型值, 则继续对该值的内部每个元素进行获取。

这里就不多做演示了,因为用递归来实现深拷贝很少,简单运用的话,用的最多的就是第二种深拷贝方式了。

  • 利用JSON函数深拷贝 实现原理: 将变量的值转变成字符串形式, 然后再转化成对象赋值给新的变量
代码语言:javascript复制
let a = {name: '张三', age: 19, like: ['打篮球', '唱歌', '跳舞']}
let b = JSON.parse(JSON.stringify(a))

a.name = '李四'
a.like[0] = '睡觉'

console.log(a)
console.log(b)
// { name: '李四', age: 19, like: [ '睡觉', '唱歌', '跳舞' ] }
// { name: '张三', age: 19, like: [ '打篮球', '唱歌', '跳舞' ] }

可以看到, 变量b 已经完全独立存在了, 无论变量a 怎么变, 变量b 都保持不变了。

结束语

好了,我已经尽可能用易懂的语言来讲述深拷贝和浅拷贝的区别了, 这也是我研究了好久,才弄明白的, 希望不懂的你们可以理解这两个概念, 如果有什么地方讲的不好, 也欢迎大家提出来,让我改正, 谢谢~

0 人点赞