1.前言:深浅拷贝“远”吗?
说起JavaScript的深浅拷贝,很多前端童靴,特别是新童靴的感觉就是这个知识点很重要,面试必问,但真正理解其切有点难度;手写也只是一味死记硬背,并不知其到底为何如此实现。本着知其然知其所以然的学习态度,咱们就从最日常的代码中来讲解它。其实,在我们日常开发中,‘使用’或者说是‘临近’深浅拷贝,出现频率还是很大的,只是你并未发现而已。其中,最常见的就是Object.assign()和JSON.parse(JSON.stringify(xxx))。是不是很意外?别慌,到底怎么回事,咱们继续往下看。
2.JavaScript数据类型:
既然是解析JavaScript的深浅拷贝,那么必离不开其‘源头’,那就是JS的两大数据类型----基本数据类型和引用数据类型。
- 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt。
- 引用数据类型:Object。
3.JavaScript两大数据类型的特点:
- 基本数据类型的特点:数据直接存储在栈中。
- 引用数据类型的特点:存储在栈中的是对象的引用地址,而真实的数据存放在堆内存中。
4.何为深浅拷贝?
狭义上的深浅拷贝只是针对引用数据类型而言的,基本数据类型只有赋值操作。
- 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 深拷贝:会创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改到原对象。
-
基本数据类型:
-
引用数据类型:
5.JavaScript之Object.assign()。
Object.assign({},obj):这就是文章开头所提及的我们日常开发中常用的一种涉及‘深浅拷贝’的方式。当然,当obj是单层对象时,此时的Object.assign()能实现“深拷贝”;而当obj为多层对象时,那么Object.assign({},obj)只能视为‘浅拷贝’。看以下案例:
- 单层对象--‘深拷贝’:
- 多层对象--‘浅拷贝’:
6.JavaScript之JSON.parse(JSON.stringify(xxx))。
JSON.parse(JSON.stringify(xxx)):这是我们日常开发中所常用的另一种能实现‘深浅拷贝’的方案。不过这种组合能实现普通对象(function,Symbol(),undefined这三种除外)的深拷贝;而实现function,Symbol(),undefined这三种时,会造成数据丢失,故也称不上是完美的深拷贝。
7.深拷贝的实现思想。
主要实现思想:通过递归思维,遍历所需进行深拷贝的对象,直到里边的都是基本数据类型,然后再进行赋值,就是深度拷贝。
8.深拷贝的手写实现。
- 方法一:适合常用对象。
//定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深度克隆---对象/数组
function deepClone(target) {
//判断拷贝的数据类型
//初始化变量result 成为最终克隆的数据
let result, targetType = checkedType(target) //这里的意思是相当于let result; let targetType = checkedType(target);
if (targetType === 'Object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
//遍历目标数据
for (let i in target) {
//获取遍历数据结构的每一项值。
let value = target[i]
//判断目标结构里的每一值是否存在对象/数组
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
//继续遍历获取到value值
result[i] = deepClone(value)
} else { //获取到value值是基本的数据类型或者是函数。
result[i] = value;
}
}
return result
}
- 方法二:适合通用对象。
// 深拷贝:对对象内部进行深拷贝,支持 Array、Date、RegExp、DOM
function deepCopy(params) {
// 如果不是对象则退出(可停止递归)
if (typeof params !== 'object') return;
// 深拷贝初始值:对象/数组
let newObj = (params instanceof Array) ? [] : {};
// 使用 for-in 循环对象属性(包括原型链上的属性)
for (let i in params) {
// 只访问对象自身属性
if (params.hasOwnProperty(i)) {
// 当前属性还未存在于新对象中时
if (!(i in newObj)) {
if (params[i] instanceof Date) {
// 判断日期类型
newObj[i] = new Date(params[i].getTime());
} else if (params[i] instanceof RegExp) {
// 判断正则类型
newObj[i] = new RegExp(params[i]);
} else if ((typeof params[i] === 'object') && params[i].nodeType === 1) {
// 判断 DOM 元素节点
let domEle = document.getElementsByTagName(params[i].nodeName)[0];
newObj[i] = domEle.cloneNode(true);
} else {
// 当元素属于对象(排除 Date、RegExp、DOM)类型时递归拷贝
newObj[i] = (typeof params[i] === 'object') ? deepCopy(params[i]) : params[i];
}
}
}
}
return newObj;
}
9.结语。
以上就是本文的全部内容,基于日常开发中最常用的案例角度来讲解了JavaScript深浅拷贝的前因后果以及深拷贝手写的实现。相信看完本文,各位童靴必定对深浅拷贝有更深的理解和认知,学会手写深拷贝的同时,也能真正做到‘知其然知其所以然’。