【JS】深拷贝的各种方式及其适用场景,不适用JSON.parse(JSON.stringify())的值有哪些?(ES6最新内容,记忆背诵内容)

JSON.parse(JSON.stringify())

简单且高频使用的方法:JSON.parse(JSON.stringify(obj))

不适用的场景:

  1. obj里面有时间对象new Date(t),使用方法后变为时间字符串'2021-11-24T07:10:01.076Z'
    obj里面有包装对象new Number(1), new String("false"), new Boolean(false),使用方法后变为其原始值"1,"false",false

  2. obj里有正则对象new RegExp()、错误对象new Error(),使用方法后变为空对象{}
    obj里有Map对象new Map()new Map([[1, 'one'], [2, 'two']])使用方法后变为空对象{}
    obj里有Set对象new Set()new Set(['1', 2])使用方法后变为 空对象{}

  3. obj里有functionundefinedsymbol作为对象值,使用方法后丢失functionundefinedsymbol
    obj里有functionundefinedsymbol作为数组元素值,使用方法后变为null
    functionundefinedsymbol作为单独的值,使用方法后会返回 undefined
    symbol 作为属性键的属性,使用方法后会丢失

  4. obj里有NaNInfinity-Infinity,使用方法后变为null

  5. obj里有BigInt(), 对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError(Uncaught TypeError: Do not know how to serialize a BigInt),参考MDN文档:BigInt在JSON中的使用

  6. JSON.stringify()只能序列化对象的可枚举的自有属性,obj中的对象由构造函数生成,使用方法后该对象__proto__不再为其构造函数的prototype,失去与其构造函数的链接,不可枚举的属性丢失

  7. 对象中存在循环引用的情况也无法正确实现深拷贝,会引发 TypeError(TypeError: Converting circular structure to JSON);

记忆背诵内容

❗❗不可用情况:

⭐对象中的对象值数组值new Date()new Number()new String()new Boolean()new RegExp()new Error()new Map()new Set()function() {}undefinedSymbol()NaNInfinity-InfinityBigInt()、不可枚举的自有属性值{ 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中的使用

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值