1. 说到JavaScript的深浅拷贝,那么必然得从其‘源头’说起----JavaScript的两大数据类型(基本数据类型和引用数据类型)。
- 基本数据类型的特点:数据直接存储在栈中。
- 引用数据类型的特点:存储在栈中的是对象的引用,而真实的数据存放在堆内存中。
- 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt。
- 引用数据类型:Object。
2. 深浅拷贝的区别。
- 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 深拷贝:会创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改到原对象。
(注:狭义上深浅拷贝只是针对引用数据类型而言的,基本数据类型只是赋值操作)
3. JavaScript中原有的方法实现局限性的“深浅拷贝”。
- Object.assign({},obj):当obj是单层对象时,此时的Object.assign()能实现“深拷贝”;而当obj为多层对象时,那么Object.assign({},obj)仅能实现“浅拷贝”。
-
JSON.parse(JSON.stringify(xxx)):这种组合能实现普通对象(function,Symbol(),undefined这三种除外)的深拷贝;而实现function,Symbol(),undefined这三种时,会造成数据丢失,故也称不上是完美的深拷贝。
4.那么,在JS中要想实现功能齐全,兼容性强的深拷贝,那么就得自己手写实现深拷贝函数(PS:这也是面试必考题,不是手写也会让你描述实现的主要思想)。
主要实现思想:通过递归思想,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
// 深拷贝:对对象内部进行深拷贝,支持 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;
}