第0章 前言
之前面试时面试官问到 JS 的浅拷贝和深拷贝,虽说之前有了解过,但因为没有总结的缘故,答得并不好,故想着把内容整理一番,便照做了。
0-1 场景导入
我们先来看一个例子如下一个例子:
let
是不是答对了呢?
没错,以上例子就是我们俗称的 浅拷贝 了。那么,为什么会出现上述这种 直接将对象赋值给一个新变量,修改新对象的值会改变原对象的值 的现象呢?这就要从JS的数据类型存储方式说起了。
0-2 回顾 · 数据类型
众所周知,JavaScript存在两种数据类型分别称为 基本数据类型 和 引用数据类型 。
- 基本数据类型:Boolean、Null、Undefined、Number、String、Symbol(ES6新增)
- 键和值均存储于栈内存中。
- 引用数据类型:Object、Array、Function、RegExp、Date等。
- 键和值的地址存储于在栈内存,值存储于堆内存。
第1章 浅拷贝
那么其实就说得通了:其实在表面上变量里是直接存储了一个对象,但实际上是存储了一个对象的地址,而这个地址指向该对象。
所以,直接将对象赋值给一个新变量,实际上是将原变量的地址赋值给了新变量,故你操作新变量的过程,实际上还是操作原对象。
例如以下示例:
// 定义一个数组
第2章 深拷贝(重难点)
2-1 完整实现
那么,如果我们就想要拷贝一个对象过去并形成独立的空间呢?
这就要用到我们的 深拷贝 技术啦。
我们已知直接将对象赋值给一个新变量只是做了浅拷贝(只拷贝对象地址)。
如何实现?看代码:
let
所以,由上例子可知,深拷贝的原理就是定义一个新的对象,遍历源对象的属性并赋给新对象的属性。
2-2 通用的简易方案
虽然我们刚刚已经实现了对象的深拷贝,但是这样封装一个函数的做法内容还是显得过于冗长了。有的时候我们也只是想深拷贝一个对象,不存在复用性。这个时候采用完整的封装函数的做法就未免显得过于愚钝。
那么,我们是否还有更加简洁的做法呢?
答案是肯定的!(这真令人振奋)
看代码:
let
如上,是不是一行代码就轻松解决了对象的深拷贝呢?(如果真有这么美好就好了...)
实际上并不然。
因为,这种做法其实会忽略值为 undefined、function 和 symbol 的内容。
所以啊,想要完整实现深拷贝的功能还是老老实实用第一种函数封装的方式吧~
2-3 只对数组适用的简易方案
虽说数组也是对象,但数组相对标准的对象就比较特殊。它们可以两种简易的方案完成数组自身的深拷贝。
2-3-1 通过 slice 实现
slice 是 Array 的 API,其本身的作用是从已有的数组中返回选定的元素。
// 定义一个数组
2-3-2 通过 concat 实现
concat 是 Array 的 API,其本身的作用是完成数组间的的连接。
// 定义一个数组
第3章 总结
- 浅拷贝
- 直接将对象赋值给新变量即可实现浅拷贝。
- 实际上是拷贝了一个对象的地址。
- 新对象的值发生改变,旧对象的值也因变而变。
- 深拷贝
- 定义一个新的对象,遍历源对象的属性并赋给新对象的属性即可实现深拷贝。
- 深拷贝是完全拷贝了原对象的内容并寄存在新的内存空间,指向新的内存地址。
- 新对象的值发生改变,旧对象不影响(因为实际上这已经是两个对象,毫无关联性了)
- 如图