一行代码
JSON.parse(JSON.stringify(data))
注意点
- 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
- 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
- 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
- JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
- 如果对象中存在循环引用的情况也无法正确实现深拷贝
数组
function(arr){return Arrary.prototype.slice.call(arr)}
考虑到数组
function deepClone(source){let type = Object.prototype.toString.call(source)if(type ==='[object Arrary]' || type ==='[object Object]'){let target = type ==='[object Arrary]'?[]:{};for(var k in source){if(source.hasOwnProperty(k)){ // 继承自身属性而不继承原型链上面的target[k] = deepClone(source[k])}}}else{return source}}
循环引用
使用数组
function deepClone(source,uniqueList=[]){ //设置默认值let type = Object.prototype.toString.call(source)if(type ==='[object Arrary]' || type ==='[object Object]'){let target = type ==='[object Arrary]'?[]:{};let flag = uniqueList.find(item=> item['source'] === source)if(flag){return flag['target']}uniqueList.push({source,target})for(var k in source){if(source.hasOwnProperty(k)){ // 继承自身属性而不继承原型链上面的target[k] = deepClone(source[k])}}}else{return source}}
什么是循环引用
var a = {name: "muyiy",book: {title: "You Don't Know JS",price: "45"},a1: undefined,a2: null,a3: 123};a.text = a //当出现这种情况时就产生了循环引用
原理
let flag = uniqueList.find(item=> item['source'] === source)if(flag){return flag['target']}uniqueList.push({source, // 父元素target // 拷贝返回的子元素,应为是对象所以值会随着调用而改变})
- 定义一个状态存储的uniqueList栈,存储每次clone的父元素,
- 因为正常情况下父元素调用完之后如果子元素如果为对象的话,则将子元素作为下一个拷贝的子元素;
也就是说父元素不会调用两次,
function deepClone(source,hash = new WeakMap()){let type = Object.prototype.toString.call(source)if(type ==='[object Arrary]' || type ==='[object Object]'){let target = Arrary.isArray(source)?[]:{}// 存在相同的话则表明循环调用了if(hash.has(source)) return hash.get(source)has.set(source,target)for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){ // 属性是否可被枚举target[key] = cloneDeep3(source[key],hash)}}}else{return source}}
解决栈爆破
方法一使用setTimeout分片
递归爆栈可以使用setTimeout分片,同步执行分片推入队列中,而且不影响后面的代码执行,
// 在之前循环引用-数组模式中改变代码
//target[i] = cloneDeep2(source[i],uniqueList) 变为
target[key] = await new Promise(resolve => {setTimeout(async() => {resolve(await cloneDeep2(source[key]))}, 0);});
此时的数据调用方法变为
var data = cloneDeep(a);data.then(v=>{console.log(v);});
使用栈结构
ar obj1 = [1, 2, 3, { a: 1, b: 2, c: [1, 2, 3] }];var deepClone = function(obj) {var root = Array.isArray(obj) ? [] : {};var nodeList = [{parent: root,key: undefined,data: obj,},];while (nodeList.length) {let node = nodeList.pop(),parent = node.parent,k = node.key,data = node.data;let result = parent;if (typeof k !== 'undefined') {result = parent[k] = Array.isArray(data)? [] : {};}for (let key in data) {if (data.hasOwnProperty(key)) {if (typeof data[key] === 'object') {nodeList.push({parent: result,key,data: data[key],});} else {result[key] = data[key];}}}}return root;};
