简单版
1. 利用JSON.parse(JSON.stringify(obj))
var obj = {a: 1, b: [2], c: {d: 4}};
var copyObj = JSON.parse(JSON.stringify(obj));
此种方法不能拷贝undefined, Function、Symbol、RegExp、Date、Set、Map等,以及不能解决循环引用;
2 简易版深拷贝(是对象就递归调用)
先来判断对象
// 判断是否是对象
function isObject(target) {
const type = typeof target;
return target !== null && type === "object";
}
function deepClone(target) {
if (isObject(target)) {
let cloneTarget = target instanceof Array ? [] : {};
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key]);
}
}
return cloneTarget;
} else {
return target;
}
}
但这种方式没有解决循环引用的问题:
var obj = {a: 1, b: [2], c: {name: 'zxx'}};
obj.obj = obj;
deepClone(obj);
执行结果如下:
很明显,因为递归进入死循环导致栈内存溢出了。
手写一个深拷贝
解决循环引用问题
解决循环引用问题,我们可以利用额外的存储空间。当需要拷贝当前对象时,先去存储空间中去找,没有的话就继续拷贝,有的话就直接返回,这样就可以解决循环引用的问题。
这里我们使用WeakMap这种数据结构(WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。可以解决垃圾回收问题)
function deepClone(target, map = new WeakMap()) {
if (isObject(target)) {
let cloneTarget = Array.isArray(target) ? [] : {};
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key], map);
}
}
return cloneTarget;
} else {
return target;
}
}
测试:
var obj = {a: 1, b: [2], c: {name: 'zxx'}};
obj.obj = obj;
deepClone(obj);
但上面只考虑了Array,Object两种数据类型,实际上所有的引用类型远远不止这两个。
其他数据类型
获取数据类型
下面方法就是获取数据类型:
因为大部分引用类型比如Array、Date、RegExp
等都重写了toString
方法,所以我们直接调用Object
原型上未被覆盖的toString()
方法,使用call
来改变this
指向来达到我们想要的效果
function getType(target) {
return Object.prototype.toString.call(target);
}
调用 | 结果 |
Object.prototype.toString.call(1) | [object Number] |
Object.prototype.toString.call("hello") | [object String] |
Object.prototype.toString.call(true) | [object Boolean] |
Object.prototype.toString.call(null) | [object Null] |
Object.prototype.toString.call(undefined) | [object Undefined] |
Object.prototype.toString.call(Symbol()) | [object Symbol] |
Object.prototype.toString.call({}) | [object Object] |
Object.prototype.toString.call([]) | [object Array] |
Object.prototype.toString.call(function() {}) | [object Function] |
Object.prototype.toString.call(new Error()) | [object Error] |
Object.prototype.toString.call(new Date()) | [object Date] |
Object.prototype.toString.call(new RegExp()) | [object RegExp] |
Object.prototype.toString.call(Math) | [object Math] |
Object.prototype.toString.call(JSON) | [object JSON] |
Object.prototype.toString.call(document) | [object HTMLDocument] |
Object.prototype.toString.call(window) | [object Window] |
还有Arguments、Set、Map、WeakSet、WeakMap等。。。
根据上面类型是否可遍历可以分为继续遍历的类型、不可以继续遍历的类型
处理可遍历类型
上面已经处理了Object、Array,另外还有Map
,Set、
Arguments等都是可以继续遍历的类型
先获取它们的初始化数据
function getInit(target) {
return new target.constructor();
}
处理可遍历类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
}
处理Set、Map
// 克隆set
if (type === setTag) {
target.forEach((value) => {
cloneTarget.add(deepClone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, deepClone(value, map));
});
return cloneTarget;
}
处理不可遍历类型
Bool
、Number
、String
、String
、Date
、Error
这几种类型我们都可以直接用构造函数和原始数据创建一个新对象:
克隆Symbol
类型:
function cloneSymbol(target) {
return Object(Symbol.prototype.valueOf.call(target));
}
克隆正则
function cloneReg(target) {
const reFlags = /\w*$/;
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
function cloneOtherType(target, type) {
const Ctor = target.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return cloneReg(target);
case symbolTag:
return cloneSymbol(target);
default:
return null;
}
}
拷贝函数
函数需要区分下箭头函数和普通函数,我们可以通过prototype
来区分,箭头函数是没有prototype
的。
function cloneFunction(func) {
let funcString = func.toString();
if (func.prototype) {
return new Function('return ' + funcString)();
} else {
return eval(funcString);
}
}
最后,完整的代码:
const boolTag = "[object Boolean]";
const dateTag = "[object Date]";
const errorTag = "[object Error]";
const numberTag = "[object Number]";
const regexpTag = "[object RegExp]";
const stringTag = "[object String]";
const symbolTag = "[object Symbol]";
const functionTag = "[object Function]";
const mapTag = "[object Map]";
const setTag = "[object Set]";
const arrayTag = "[object Array]";
const objectTag = "[object Object]";
const argsTag = "[object Arguments]";
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
// 判断是否是对象
function isObject(target) {
const type = typeof target;
return target !== null && (type === "object" || type === "function");
}
// 获取它们的初始化数据
function getInit(target) {
return new target.constructor();
}
// 获取实际类型
function getType(target) {
return Object.prototype.toString.call(target);
}
// 拷贝Symbol
function cloneSymbol(target) {
return Object(Symbol.prototype.valueOf.call(target));
}
// 拷贝RegExp
function cloneReg(target) {
const reFlags = /\w*$/;
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
// 拷贝function
function cloneFunction(func) {
let funcString = func.toString();
// 区分箭头函数和普通函数
if (func.prototype) {
return new Function("return " + funcString)();
} else {
return eval(funcString);
}
}
// 拷贝其他数据类型(不可遍历类型)
function cloneOtherType(target, type) {
const Ctor = target.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return cloneReg(target);
case symbolTag:
return cloneSymbol(target);
case functionTag:
return cloneFunction(target);
default:
return null;
}
}
function deepClone(target, map = new WeakMap()) {
// 原始类型直接返回
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
// 是否可遍历
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循环引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// set
if (type === setTag) {
target.forEach((value) => {
cloneTarget.add(deepClone(value, map));
});
return cloneTarget;
}
// map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, deepClone(value, map));
});
return cloneTarget;
}
// 克隆对象和数组
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key], map);
}
}
return cloneTarget;
}
测试:
var map = new Map();
map.set("name", "zxx");
var set = new Set();
set.add("xxx");
var obj = {
num: 1,
str: "hello",
bool: true,
symbol: Symbol("test"),
arr: [1, 2, 3],
o: { name: "zxx" },
map: map,
set: set,
regExp: /\d+/,
date: new Date(),
arrowFn: () => {
console.log("arrowFn");
},
fn: function (a, b, c) {
return a + b - c;
},
error: new Error("error"),
};
obj.obj = obj;
deepClone(obj);
输出结果: