济南网站建设网站,常州市建设工程管理中心网站,wordpress手机全部显示,专业的营销型网站最新报价文章目录 为什么要使用递归什么深拷贝具体实现基础实现处理 函数处理 Symbol处理 Set处理 Map处理 循环引用 结语-源码 为什么要使用递归什么深拷贝 我们知道在 JavaScript 中可以通过使用JSON序列化来完成深拷贝#xff0c;但是这种方法存在一些缺陷#xff0c;比如对于函数… 文章目录 为什么要使用递归什么深拷贝具体实现基础实现处理 函数处理 Symbol处理 Set处理 Map处理 循环引用 结语-源码 为什么要使用递归什么深拷贝 我们知道在 JavaScript 中可以通过使用JSON序列化来完成深拷贝但是这种方法存在一些缺陷比如对于函数、Symbol、Map 等等数据是无法处理的甚至如果是循环引用的话还会造成报错具体关键JSON的介绍我就不在这里赘述了有兴趣的可以看看我的另一篇文章JSON详解 具体实现
基础实现 我们先实现一下最简单的只对普通数据进行处理数据如下 const obj {name: 张三,age: 18,friends: [{ name: 李四, age: 20 },{ name: 王五, age: 22 }],other:{address:长沙}
}我们肯定要有一个这样的方法或者函数来帮助我们完成这个深拷贝如果要两个对象要不关联的话那就只能是创建一个全新的对象全新的对象怎么来对吧所以我们就可以写出如下代码 function deepClone(value) {// 创建对象-用来承载数据const newObj {}
}现在的问题就回到了。我们如何把 obj 的数据赋值给 newObj肯定不能是直接 newObj obj我们要做的就是拿到 obj 对象中的 k、v通过这个 k、v 重新给 newObj 这个对象创建数据但是我又不知道这个传入进来的对象具备什么属性和值所以怎么得到这个 k、v 呢而这种取出一个数据中的每一项是不是感觉和数组的遍历很像呢所以如果可以遍历这个对象的话是不是就可以拿到呢 不知道大家有没有记得以前 js 基础接触过的 for…in 方法这个方法就可以遍历对象可以遍历对象是不是就表示可以把 obj 所有有的属性和值也给 newObj 对象赋值一下呢所以我们下一步就应该是使用 for…in 来完成如下 function deepClone(value) {const newObj {}// 遍历 value 赋值给 newObjfor (let key in value) {newObj[key] value[key]}return newObj
}现在是不是实现了我们的效果呢不知道打印结果看一下吧添加测试代码如下 const newObj deepClone(obj)console.log(newObj: , newObj)结果如图 值确实是一样的但是是不是可以实现那种修改 newObj 而不影响 obj 呢测试一下吧测试代码如下 console.log(newObj obj)console.log(newObj.friends obj.friends)console.log(newObj.other obj.other)newObj.other.address 上海console.log(newObj.other.address)console.log(obj.other.address)输出结果如图 这就非常的有意思了newObj 确实不等于 obj 了表示最外层的引用确实断开了但是 friends 和 other 属性确还是相等的且改变了 newObj.other.address 的值obj 这个地方的值也改变了 其实细心一点就不难发现firends 和 other 属性也都是一个对象啊所以我们是不是也要对他们进行处理呢那怎么处理呢针对这种情况和我们处理这个外层的其实是不是一致的啊而重复这个过程哪想到了什么对本文的主题递归所以如果检测到一个值是 对象 的话就再次递归这个函数执行修改代码如下 function deepClone(value) {const newObj {}for (let key in value) {// 判断属性值是否为对象如果是对象则递归调用deepClone函数newObj[key] typeof value[key] object ? deepClone(value[key]) : value[key]}return newObj
}查看克隆的结果如图 从结果上看 friends 这个属性值从数组变成了对象这是因为数组也是一个对象而我们处理数组的方式也是重新创建一个 newObj 来存储数组的数据所以返回的值自然就变成了对象这时候应该怎么解决呢 问题尽然是最开始的 newObj 被赋值为了一个 {}那么我们只要判断当前克隆的值是一个数组的时候就赋值为 []否则赋值为 {}是不是就可以解决了如下 function deepClone(value) {// 如果是数组则创建一个新的数组否则创建一个新的对象const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] typeof value[key] object ? deepClone(value[key]) : value[key]}return newObj
}结果如图 现在我们不改动测试语句查看测试结果如图 是不是感觉比较简单呢剩下的我们需要做一下优化判断是否是一个对象可以抽离成一个方法且可以用于开始的边界判断如下 function isObject(value) {return typeof value object value ! null
}然后使用这个方法优化 deepClone 方法如下 function deepClone(value) {if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}return newObj
}处理 函数 为了更加直观的看到处理函数类型我们先暂时对克隆的对象做一些修改如下 const obj {name: 张三,age: 18,sayHi: function () {console.log(你好啊)}
}首先对于这个函数的处理我们需要先认清楚一些概念如果一个函数内部的逻辑非常复杂我们要通过创建一个新的 Funciton 来实现一个完全的新的函数也是可以的但是会非常的复杂而且没有什么必要一个函数本身就具备复用性内部的作用域本身就互不干扰所以我们只要获取到这个函数并重新赋值给新拷贝的对象里面的对应的位置即可这也是一种目前比较常见的做法 因此我们只要发现当前克隆的值是一个函数的话直接返回即可如下 function deepClone(value) {// 判断是否是一个函数if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}return newObj
}现在输出打印克隆的打印结果如图
处理 Symbol 我们还是一样先修改一下数据如下 const s1 Symbol(aaa)
const s2 Symbol(bbb)const obj {name: 张三,age: 18,[s1]: aaa,s2: s2
}我们使用目前的方法来看一下克隆的结果如图 使用 s1 作为键的属性没有被拷贝进来但是使用字符串 s2 的属性和携带的 Symbol 类型的值被拷贝了进来而且这两个对象里面的 s2 这个 Symbol值是不是同一个呢测试代码如下 console.log(newObj.s2 obj.s2) // true就不粘贴结果了测试中得出的还是同一个 Symbol所以如果不希望使用同一个 Symbol 的话我们可以在最开始的时候进行一个判定如果当前克隆的值是一个 Symbol 的话就重新创建 Symbol并使用原来的 Symbol 的 description如下 function deepClone(value) {// 判断是否是一个 Symbolif (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}return newObj
}查看输出结果如下 console.log(newObj.s2 obj.s2) // false那为什么 Symbol 作为 key 的时候无法被拷贝呢这是因为一个 Symbol 是无法被遍历的如图 那如果获取这个 Symbol 呢如图 具体是不是可以呢我们写一些代码测试一下如图 所以我们现在需要针对 Symbol 为key时进行一些特殊的处理如下 function deepClone(value) {if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}// 获取对象上所有的为 Symbol 类型的keyconst symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {// 赋值调用当前函数即可// - 至于这个 key 是不是要重新 Symbol 就看自己的需求了newObj[sk] deepClone(newObj[sk])}return newObj
}查看结果如图
处理 Set 修改数据如下 const obj {name: 张三,age: 18,set: new Set([1, 2, 3])
}拷贝的结果如图 set 变了一个空对象这肯定不是我们需要的结果那就还需要对是一个 set 类型的时候进行处理这个也很简单 function deepClone(value) {if (value instanceof Set) {return new Set([...value])}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] deepClone(newObj[sk])}return newObj
}结果如图 但是这种赋值的方法是存在一些隐患的比如我们存入的是 set 是对象呢首先改变数据我们看一下代码如下 const obj {name: 张三,age: 18,set: new Set([1, 2, 3]),sets: new Set()
}const o1 { a: 1, b: 2 }
const o2 { c: 3, d: 4 }
obj.sets.add(o1)
obj.sets.add(o2)我们按照现在的方法看一下克隆的结果测试代码如下 const newObj deepClone(obj)console.log(newObj)const arr1 []for (const [key, value] of newObj.sets.entries()) {arr1.push(value)
}console.log(arr1[0] o1)arr1[0].a 100
console.log(o1)结果如图 可以看到 o1 这个对象是受到了影响的所以我们需要单独对这个赋值的过程进行一些处理如下 function deepClone(value) {if (value instanceof Set) {// 创建一个数组来存储值const list []// 通过 forEach 方法将 Set 中的值复制到数组中value.forEach(item {// 而这个值通过递归在处理一次list.push(deepClone(item))})// 创建一个新的 Set 对象并将数组中的值作为参数传入return new Set(list)}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] deepClone(newObj[sk])}return newObj
}现在我们再看一下处理的结果如图
处理 Map 修改的数据如下 const obj {name: 张三,age: 18,map: new Map()
}const o1 { a: 1, b: 2 }
const o2 { c: 3, d: 4 }
const o3 { name: ls, age: 22 }obj.map.set(o1, o1)
obj.map.set(o2, o3)如果不处理 map看看打印的结果如图 这个结果一样也不是我们所期望的但是 set 和 map 处理的方式其实都是差不多的所以我就直接展示代码了如下 function deepClone(value) {if (value instanceof Set) {const list []value.forEach(item {list.push(deepClone(item))})return new Set(list)}// 处理 mapif (value instanceof Map) {const myMap new Map()for (const [key, _val] of value) {// 这里的 key 是不是需要进行再次的递归处理就取决于自己的需求了// - 一般是不需要再次做额外的处理的const newValue deepClone(_val)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list []value.forEach(item {list.push(deepClone(item))})return new Map(list)}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}const newObj Array.isArray(value) ? [] : {}for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] deepClone(newObj[sk])}return newObj
}结果如图 测试结果如下 console.log(newObj.map.get(o1) obj.map.get(o1)) // false处理 循环引用 修改数据如下 const obj {name: 张三,age: 18
}// my 属性引用自身
obj.my obj查看输出的结果如下 直接就报了栈溢出的错误这就是每次递归拷贝的都是 obj 本身而每个 obj 都具备 my 属性指向自身递归没有终止条件自然就会报错 这个 my 是通过 obj.my obj 实现的那么是不是表示我们在创建了一个 newObj 的时候也只是需要把 newObj 这个对象自身赋值给 newObj.my newObj 就可以了而不需要一直的拷贝 为了实现这个我们就需要来分析一下了怎么完成 newObj.my newObj 这一步操作要完成这个操作首先我们就要能够获取到 newObj 这个对象这是第一点第二个条件就是我们还需要能够保存最开始克隆的原始对象 obj而能够实现这一点的要求的我们就可以联想到 map 或者 weakmap这里保证 obj 存储的时候就是弱引用而不会外界需要销毁的时候导致无法销毁使用 weakpack 会更加的合适 所以我们应该有这么一个操作手动创建一个 weakmap然后再 deepClone 第一次创建了 newObj 的时候就进行存储wm.set(obj, newObj)而当存在一个这样的数据的时候我们在就可以在 deepClone 方法上加上一个条件if(当vm中存在obj这个k的时) { return 就返回存储的 newObj 这个值 } 梳理了过程就可以回到具体的代码实现如下 // 全局创建 WeakMap
const wm new WeakMap()function deepClone(value) {if (value instanceof Set) {const list []value.forEach(item {list.push(deepClone(item))})return new Set(list)}if (value instanceof Map) {const myMap new Map()for (const [key, _val] of value) {const newValue deepClone(_val)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list []value.forEach(item {list.push(deepClone(item))})return new Map(list)}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}// 当拷贝到 obj.my 这个属性的时候由于 obj.my 的值就是 obj 本身// - 所以此时传入的值就是 obj而其他属性如果没有引用自身的话就不会存在// - 就可以通过判断 weakmap 中是否存在这个数据如果存在就直接返回 weakmap 中一开始存储的值if (wm.has(value)) {return wm.get(value)}const newObj Array.isArray(value) ? [] : {}// 创建 newObj 的时候以需要克隆的初始值作为 keynewObj 作为 valuewm.set(value, newObj)for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key]) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] deepClone(newObj[sk])}return newObj
}结果如图 基于此我们就可以实现 newObj.my.my.my… 的操作如图 而进行到这一步还有一个需要的地方由于我们这个 weakmap 是全局的就会导致如果在实际的使用中多次调用 deepClone 这个方法的时候weakmap 这个里面的数据就会越来越多而实际当完成拷贝的时候这个数据就不用存在了 因此我们可以改写一下如下 // 通过参数实现创建 weakmap只要没有传递就会自动创建而如果没有传递了则不会创建就会使用传递的 weakmap
function deepClone(value, wm new WeakMap()) {if (value instanceof Set) {const list []value.forEach(item {// 传递 weakmaplist.push(deepClone(item, wm))})return new Set(list)}if (value instanceof Map) {const myMap new Map()for (const [key, _val] of value) {// 传递 weakmapconst newValue deepClone(_val, wm)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list []value.forEach(item {// 传递 weakmaplist.push(deepClone(item, wm))})return new Map(list)}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}if (wm.has(value)) {return wm.get(value)}const newObj Array.isArray(value) ? [] : {}wm.set(value, newObj)for (let key in value) {// 同时在内部调用的时候为了防止后续调用在创建 weakmap我们在这里调用的时候就要把第一次执行 deepClone 创建的 weakmap 传递进去newObj[key] isObject(value) ? deepClone(value[key], wm) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {// 传递 weakmapnewObj[sk] deepClone(newObj[sk], wm)}return newObj
}结果如图 在效果上也是没有问题的
结语-源码 我在测试中都是单独每一项数据进行测试的是为了更好的观测实际一个对象都包含这些数据的话也都是 ok的需要的话可以自己测试而且写下来就会发现其实逻辑都是差不多的可以根据你实际的情况进行增加或者减免在日常的开发中使用 JSON 序列化一般也可满足我们的需求不过知道不用和不知道还是存在本质的区别的可能现在有些你学习的技术没有实际的意义但是只有积累的足够多的时候你才能完成一些本质上的突破 很多事情不是因为看到了希望才去坚持而是因为坚持了才能看到希望 function deepClone(value, wm new WeakMap()) {if (value instanceof Set) {const list []value.forEach(item {list.push(deepClone(item, wm))})return new Set(list)}if (value instanceof Map) {const myMap new Map()for (const [key, _val] of value) {const newValue deepClone(_val, wm)myMap.set(key, newValue)}return myMap}if (value instanceof Map) {const list []value.forEach(item {list.push(deepClone(item, wm))})return new Map(list)}if (typeof value symbol) {return Symbol(value.description)}if (typeof value function) {return value}if (!isObject(value)) {return value}if (wm.has(value)) {return wm.get(value)}const newObj Array.isArray(value) ? [] : {}wm.set(value, newObj)for (let key in value) {newObj[key] isObject(value) ? deepClone(value[key], wm) : value[key]}const symKeys Object.getOwnPropertySymbols(value)for (const sk of symKeys) {newObj[sk] deepClone(newObj[sk], wm)}return newObj
}