深拷贝的各种方式及其适用场景
JSON.parse(JSON.stringify())
简单且高频使用的方法:JSON.parse(JSON.stringify(obj))
不适用的场景:
-
obj里面有时间对象
new Date(t)
,使用方法后变为时间字符串'2021-11-24T07:10:01.076Z'
;
obj里面有包装对象new Number(1)
,new String("false")
,new Boolean(false)
,使用方法后变为其原始值"1,"false",false
; -
obj里有正则对象
new RegExp()
、错误对象new Error()
,使用方法后变为空对象{}
;
obj里有Map对象new Map()
,new Map([[1, 'one'], [2, 'two']])
使用方法后变为空对象{}
;
obj里有Set对象new Set()
,new Set(['1', 2])
使用方法后变为 空对象{}
; -
obj里有
function
,undefined
和symbol
作为对象值,使用方法后丢失function
、undefined
或symbol
;
obj里有function
,undefined
和symbol
作为数组元素值,使用方法后变为null;
function
,undefined
和symbol
作为单独的值,使用方法后会返回 undefined;
symbol
作为属性键的属性,使用方法后会丢失; -
obj里有
NaN
、Infinity
和-Infinity
,使用方法后变为null; -
obj里有
BigInt()
, 对任何BigInt
值使用JSON.stringify()
都会引发 TypeError(Uncaught TypeError: Do not know how to serialize a BigInt
),参考MDN文档:BigInt在JSON中的使用 -
JSON.stringify()
只能序列化对象的可枚举的自有属性,obj中的对象由构造函数生成,使用方法后该对象__proto__
不再为其构造函数的prototype
,失去与其构造函数的链接,不可枚举的属性丢失; -
对象中存在循环引用的情况也无法正确实现深拷贝,会引发 TypeError(
TypeError: Converting circular structure to JSON
);
记忆背诵内容
❗❗不可用情况:
⭐对象中的对象值或数组值为new Date()
、new Number()
、 new String()
、 new Boolean()
、new RegExp()
、new Error()
、new Map()
、new Set()
、function() {}
、undefined
、Symbol()
、NaN
、Infinity
、-Infinity
、BigInt()
、不可枚举的自有属性值{ value: 'json', enumerable: false }
(例如,构造函数构造的对象的__proto__
),循环引用的值let obj = {data: obj}
不可用此方法;
⭐对象中的对象键为Symbol
( { [Symbol.for("json")]: "stringify" }
)不可用此方法;
⭐对象为function() {}
、DOM元素(var box = document.getElementById("box"))
、RegExp()
等广义上的对象时不可用此方法。
自己编写深拷贝方法
如果出现以上不可用的情况,我们就需要自己写深拷贝方法了
简陋的基本实现:
function getType(target) {
return Object.prototype.toString.call(target);
}
function deepClone(data) {
const type = getType(data);
let obj;
if (type === '[object Array]') {
obj = [];
} else if (type === '[object Object]') {
obj = {};
} else {
// 不再具有下一层次
return data;
}
// 对原型上的方法也拷贝了....
for (const key in data) {
obj[key] = this.deepClone(data[key]);
}
return obj;
}
考虑循环引用,和内存占用:
function getType(target) {
return Object.prototype.toString.call(target);
}
function deepClone(data, weakMap: new WeakMap()) {
const type = getType(data);
let obj;
if (type === '[object Array]') {
obj = [];
} else if (type === '[object Object]') {
obj = {};
} else {
// 不再具有下一层次
return data;
}
if (weakMap.get(data)) {
return weakMap.get(data);
}
weakMap.set(data, obj);
// 对原型上的方法也拷贝了....
for (const key in data) {
obj[key] = this.deepClone(data[key], weakMap);
}
return obj;
}
⭐ Object.prototype.toString.call()获取各数据类型的结果列举
考虑被拷贝值的类型,Object.prototype.toString.call(target)
可能有获取各种类型结果;
调用 | 结果 |
---|---|
Object.prototype.toString.call(true) | [object Boolean] |
Object.prototype.toString.call(123) | [object Number] |
Object.prototype.toString.call(“Sangrdli”) | [object String] |
Object.prototype.toString.call(null) | [object Null] |
Object.prototype.toString.call(undefined) | [object Undefine] |
Object.prototype.toString.call(Symbol()) | [object Symbol] |
Object.prototype.toString.call(BigInt(2)) | [object Bigint] |
Object.prototype.toString.call({}) | [object Object] |
Object.prototype.toString.call(function() {}) | [object Function] |
Object.prototype.toString.call([]) | [object Array] |
Object.prototype.toString.call(new class {}) | [object Object] |
Object.prototype.toString.call(new Error()) | [object Error] |
Object.prototype.toString.call(new RegExp()) | [object RegExp] |
Object.prototype.toString.call(new Date()) | [object Date] |
Object.prototype.toString.call((function() { return arguments; })()) | [object Arguments] |
Object.prototype.toString.call(Math) | [object Math] |
Object.prototype.toString.call(JSON) | [object JSON] |
Object.prototype.toString.call(document) | [object HTMLDivElement] |
Object.prototype.toString.call(new Document()) | [object Document] |
Object.prototype.toString.call(window) | [object Window] |
下面我们抽离出一些常用的数据类型以便后面使用:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
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 deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
在上面的集中类型中,我们简单将他们分为两类:
- 可以继续遍历的类型
- 不可以继续遍历的类型
我们分别为它们做不同的拷贝。
考虑可继续遍历的类型
这里我们只考虑Object、Array、Map,Set四种
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
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 deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function deepClone(data, weakMap: new WeakMap()) {
const type = getType(data);
let obj;
if (deepTag.includes(type)) {
obj = getInit(data, type);
} else {
// 不再具有下一层次
return data;
}
// 循环引用时
if (weakMap.get(data)) {
return weakMap.get(data);
}
weakMap.set(data, obj);
// 克隆set
if (type === setTag) {
data.forEach(value => {
obj.add(deepClone(value, map));
});
return obj;
}
// 克隆map
if (type === mapTag) {
data.forEach((value, key) => {
obj.set(key, deepClone(value, map));
});
return obj;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(data);
forEach(keys || data, (value, key) => {
if (keys) {
key = value;
}
obj[key] = deepClone(data[key], map);
});
return obj;
}
考虑不循环类型:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
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 deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function deepClone(data, weakMap: new WeakMap()) {
// 克隆原始类型
if (!isObject(target)) {
return target;
}
const type = getType(data);
let obj;
if (deepTag.includes(type)) {
obj = getInit(data, type);
} else {
// 不再具有下一层次
return cloneOtherType(data, type)
}
// 循环引用时
if (weakMap.get(data)) {
return weakMap.get(data);
}
weakMap.set(data, obj);
// 克隆set
if (type === setTag) {
data.forEach(value => {
obj.add(deepClone(value, map));
});
return obj;
}
// 克隆map
if (type === mapTag) {
data.forEach((value, key) => {
obj.set(key, deepClone(value, map));
});
return obj;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(data);
forEach(keys || data, (value, key) => {
if (keys) {
key = value;
}
obj[key] = deepClone(data[key], map);
});
return obj;
}
参考:
你不知道的 JSON.stringify() 的威力
如何写出一个惊艳面试官的深拷贝?
如何写出一个惊艳面试官的深拷贝?–git代码
关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑
MDN文档:BigInt在JSON中的使用