💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 概念 其实深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据 ~~~ // 基本类型 var a = 1; var b = a; a = 2; console.log(a, b); // 2, 1 ,a b指向不同的数据 // 引用类型指向同一份数据 var a = {c: 1}; var b = a; a.c = 2; console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据 ~~~ 对于引用类型,会导致a b指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个,有时候这可能不是我们想要的结果,如果对这种现象不清楚的话,还可能造成不必要的bug 那么如何切断a和b之间的关系呢,可以拷贝一份a的数据,根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝 ~~~ var a1 = {b: {c: {}}; var a2 = shallowClone(a1); // 浅拷贝 a2.b.c === a1.b.c // true var a3 = clone(a1); // 深拷贝 a3.b.c === a1.b.c // false ~~~ <br> # 深克隆需要注意的问题 * 入参类型检查 * 当数据量较大并层次很深时,使用递归函数会导致栈溢出,而此处又无法使用尾递归,该怎么处理 * typeof Date,Math,RegExp,Function,Null 都返回Object 该怎么处理 * Date,RegExp,Function 应该如何克隆 * 当对象的两个属性v,s引用同一个对象时,克隆之后也应该引用同一个对象 * 对象的原型prototype 如何克隆 * 属性的getOwnPropertyDescriptor如何克隆 * for-in遍历的是原型链,需要用hasOwnProperty 判断是否是自有属性 * … # 浅拷贝 ## 方法一 ~~~ function shallowClone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; } ~~~ ## 方法二 ~~~ Object.assign(target, ...sources) ~~~ ## 方法三 ~~~ let a = { age: 1 } let b = { ...a } a.age = 2 console.log(b.age) // 1 ~~~ <br> # 深拷贝 ## 方法一 ~~~ function clone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { if (typeof source[i] === 'object') { target[i] = clone(source[i]); // 注意这里 } else { target[i] = source[i]; } } } return target; } ~~~ 问题 * 没有对参数做检验 * 判断是否对象的逻辑不够严谨 * 没有考虑数组的兼容 * **没有处理栈溢出** * 没有处理**循环引用** ## 方法二 ~~~ function cloneJSON(source) { return JSON.parse(JSON.stringify(source)); } ~~~ 问题 * **没有处理栈溢出** * 非JSON格式的值(undefined、函数等)会被忽略 ## 方法三 ~~~ // 定义函数获取数据类型 function _getDataType(data) { return Object.prototype.toString.call(data).slice(8, -1); } ~~~ ~~~ // 定义函数克隆RegExp类型 function copyRegExp(regExp) { let attrs = ''; if (regExp.global) attrs += 'g'; if (regExp.ignoreCase) attrs += 'i'; if (regExp.multiline) attrs += 'm'; let newRegExp = new RegExp(regExp, attrs); newRegExp.lastIndex = regExp.lastIndex; return newRegExp; } ~~~ ~~~ // 定义深克隆函数 function clone(x) { // String Number Boolean Undefined Null 返回自身 if (x == null || typeof x !== 'object') return x; // RegExp Date Function 克隆 let type = _getDataType(x); let root; switch (type) { case 'RegExp': return copyRegExp(x); case 'Date': return new Date(x.getTime()); case 'Function': return x; case 'Array': root = []; break; default: root = Object.create(Object.getPrototypeOf(x)); } // Array Object 克隆 // 用来去重 解决原数据中多个属性引用同一对象克隆后不相同问题 const uniqueList = []; // 使用栈结构解决递归爆栈问题 const stack = [ { parent: root, key: undefined, data: x, } ]; // 深度优先循环 while (stack.length) { const {parent, key, data} = stack.pop(); // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素 let res = parent; if (typeof key !== 'undefined') { let type = _getDataType(data); switch (type) { case 'RegExp': parent[key] = copyRegExp(data); continue; case 'Date': parent[key] = new Date(data.getTime()); continue; case 'Function': parent[key] = data; continue; case 'Array': res = parent[key] = []; break; default: let proto = Object.getPrototypeOf(data); res = parent[key] = Object.create(proto); } } //数据引用已经存在则赋值并退出本次循环,不存在则缓存 let uniqueData = uniqueList.find(item => item.source === data); if (uniqueData) { parent[key] = uniqueData.target; continue; } else { uniqueList.push({ source: data, target: res, }); } for (let k in data) { if (data.hasOwnProperty(k)) { if (data[k] == null || typeof data[k] !== 'object') { // 基础类型克隆 let descriptor=Object.getOwnPropertyDescriptor(data,k); Object.defineProperty(res,k,descriptor); } else { // 引用类型加入stack循环处理 stack.push({ parent: res, key: k, data: data[k], }); } } } } return root; } ~~~ # 参考资料 [深拷贝的终极探索(90%的人都不知道)](https://juejin.im/post/5bc1ae9be51d450e8b140b0c) [js 深克隆(考虑到类型检查,递归爆栈,相同引用,Date和Function等特殊类型克隆,原型克隆)](https://blog.csdn.net/lyt_angularjs/article/details/86599820)