function clone(obj, cloned = new WeakMap()) {if (cloned.has(obj)) return cloned.get(obj)if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) {return obj}let newObj = null// 时间对象,正则if (obj.constructor === Date || obj.constructor === RegExp) {newObj = new obj.constructor(obj)} else if (typeof obj === 'function') {newObj = eval('(' + obj.toString() + ')')} else if (obj.clone) {// 兼容Moments等库return obj.clone()} else { //数组,普通对象newObj = new obj.constructor()cloned.set(obj, newObj) // 标记改对象已经复制过for (let key in Object.getOwnPropertyDescriptors(obj)) {newObj[key] = clone(obj[key], cloned)}}return newObj}// 1、函数,日期,正则let obj = {a: 1,fn: function() {console.log('test')},date: new Date(),reg: /\d/}let o = clone(obj)console.log(o)console.log(obj)console.log(o.fn === obj.fn)console.log(o.date === obj.date)console.log(o.reg === obj.reg)// 2、循环引用let a = {}a.b = aconsole.log(clone(a))
…
const isComplexDataType = obj => (typeof obj === ‘object’ || typeof obj === ‘function’) && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== ‘function’) ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}
// 下面是验证代码
let obj = {
num: 0,
str: ‘’,
boolean: true,
unf: undefined,
nul: null,
obj: { name: ‘我是一个对象’, id: 1 },
arr: [0, 1, 2],
func: function () { console.log(‘我是一个函数’) },
date: new Date(0),
reg: new RegExp(‘/我是一个正则/ig’),
[Symbol(‘1’)]: 1,
};
Object.defineProperty(obj, ‘innumerable’, {
enumerable: false, value: ‘不可枚举属性’ }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log(‘obj’, obj)
console.log(‘cloneObj’, cloneObj)
vuex中的深拷贝解决循环引用的问题:(没有考虑函数)
function find (list, f) {return list.filter(f)[0]}/*** Deep copy the given object considering circular structure.* This function caches all nested objects and its copies.* If it detects circular structure, use cached copy to avoid infinite loop.** @param {*} obj* @param {Array<Object>} cache* @return {*}*/function deepCopy (obj, cache = []) {// just return if obj is immutable valueif (obj === null || typeof obj !== 'object') {return obj}// if obj is hit, it is in circular structureconst hit = find(cache, c => c.original === obj)if (hit) {return hit.copy}const copy = Array.isArray(obj) ? [] : {}// put the copy into cache at first// because we want to refer it in recursive deepCopycache.push({original: obj,copy})Object.keys(obj).forEach(key => {copy[key] = deepCopy(obj[key], cache)})return copy}
以上都是通过递归来实现的,我们都知道递归有个缺点,函数每次调用都会形成调用记录,容易爆栈。是不是所有的递归都能用while循环解决呢???
把一个对象看成一棵树:
var a = {a1: 1,a2: {b1: 1,b2: {c1: 1}}}
a/ \a1 a2| / \1 b1 b2| |1 c1|1
思路:用循环遍历一棵树,需要借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点
首先我们往栈里放入种子数据,key用来存储放哪一个父元素的那一个子元素拷贝对象
然后遍历当前节点下的子元素,如果是对象就放到栈里,否则直接拷贝
function cloneLoop(x) {const root = {};// 栈const loopList = [{parent: root,key: undefined,data: x,}];while(loopList.length) {// 深度优先const node = loopList.pop();const parent = node.parent;const key = node.key;const data = node.data;// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素let res = parent;if (typeof key !== 'undefined') {res = parent[key] = {};}for(let k in data) {if (data.hasOwnProperty(k)) {if (typeof data[k] === 'object') {// 下一次循环loopList.push({parent: res,key: k,data: data[k],});} else {res[k] = data[k];}}}}return root;}
解决循环引用:
// 保持引用关系function cloneForce(x) {// =============const uniqueList = []; // 用来去重// =============let root = {};// 循环数组const loopList = [{parent: root,key: undefined,data: x,}];while(loopList.length) {// 深度优先const node = loopList.pop();const parent = node.parent;const key = node.key;const data = node.data;// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素let res = parent;if (typeof key !== 'undefined') {res = parent[key] = {};}// =============// 数据已经存在let uniqueData = find(uniqueList, data);if (uniqueData) {parent[key] = uniqueData.target;continue; // 中断本次循环}// 数据不存在// 保存源数据,在拷贝数据中对应的引用uniqueList.push({source: data,target: res,});// =============for(let k in data) {if (data.hasOwnProperty(k)) {if (typeof data[k] === 'object') {// 下一次循环loopList.push({parent: res,key: k,data: data[k],});} else {res[k] = data[k];}}}}return root;}function find(arr, item) {for(let i = 0; i < arr.length; i++) {if (arr[i].source === item) {return arr[i];}}return null;}
以上两段代码转载自:
深拷贝的终极探索(90%的人不知道):https://zhuanlan.zhihu.com/p/46789186
