由简入繁
今天花点时间,来整理一波深复制的skr操作。由简入繁 实现一个不会循环引用的deepClone方法
深复制&&潜复制
文章开头,先扫个盲,仔细看下面这个例子
var obj = {a: 1, b: 2, c: { a: 3 },d: [4, 5]}
var obj1 = obj
var obj2 = JSON.parse(JSON.stringify(obj))//深拷贝常用方法
var obj3 = {...obj}
var obj4 = Object.assign({},obj)
obj.a = 999
obj.c.a = -999
obj.d[0] = 123
console.log(obj1) //{a: 999, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj2) //{a: 1, b: 2, c: { a: 3 },d: [4, 5]}
console.log(obj3) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj4) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
发现什么问题了吗?这些操作好像只是遍历了树的第一层,然后将指针指向了新的对象
- 这里有个知识点:有人可能会觉得基本类型 是在栈中开辟一个新空间存放,其实obj.a 和obj1.a 开始用的是同一个‘1’,后面进行obj.a=111操作的时候 开辟一个新的空间存放999,obj1.a还是存放‘1’
单纯可爱001
以下代码可能会忽略类型校验、各种兼容,只要看思路就好0.0
// 处理对象 var obj = { a: {b: {c: 123}} }
function clone1 (obj) {
var result = {}
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === 'object') {
result[i] = clone(obj[i])
} else {
result[i] = obj[i]
}
}
}
return result
}
- 这里的类型判断有点问题,完整版本看这里
- 想知道为什么用hasOwnProperty,请戳这里
- 其实还可以思考下,如果兼容数组 要怎么做,如果是es6的 还会有set、map、weakset、weakmap
上面这这种方法 长相丑陋,而且在深度优先或者循环引用的时候 容易栈溢出(貌似广度优先不会),所以我们要想个办法
暂时思路是:破坏引用或者做循环引用检查,我们接下来会重点介绍前者。
简单粗暴002
项目中,很多人会通过JSON提供的两个方法来实现深复制
JSON.parse(JSON.stringify(obj))
var a = {}
a.a = a
JSON.parse(JSON.stringify(a)) // Uncaught TypeError: Converting circular structure to JSON
但是通过测试发现,复制深度是1000000的对象,直接栈溢出了,说明底层也是使用的递归的方式。而且还一个有趣的现象,JSON内部做了循环引用检查
- JSON不可以复制对象的方法,莫慌 猛戳这里找黑科技
高冷帅气003
想要破解递归爆栈,方法只有一个:不用递归。我们可以尝试下上学时候老师教的链表还有堆栈
我们把一个对象旋转90°,像极了一棵树,那么我们面对的问题就成了 复制一个树
链表太复杂了,还有记录指针,我们可以通过栈+js对象这个技巧,规避掉还原树的时候 找不到节点顺序关系的难题,假装看不到注释的代码
function deepClone (obj) {
// const store = []
let root = Array.isArray(obj) ? [] : {}
const loopList = [{parent: root, key: undefined, data: obj}]
while (loopList.length) {
// 深度优先 广度优先没影响吧
const node = loopList.pop()
const parent = node.parent
const key = node.key
const data = node.data
let resource = parent
if (typeof key !== 'undefined') {
// 如果只是对象 这里可以使用连续赋值
resource = parent[key] = Array.isArray(data) ? [] : {}
}
// let exitedData = find(store, data)
// if (exitedData) {
// parent[key] = exitedData.target
// continue
// }
// store.push({ data, target: resource })
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
loopList.push({parent: resource, key: k, data: data[k]})
} else {
resource[k] = data[k]
}
}
}
}
return root
}
var O = {
aaa: {bbb: {ccc: 456},},
ddd: [123, 456, 789]
}
var A = [
{aaa: {bbb: {ccc: 456},}},
132
]
var ooo = deepClone(O)
var aaa = deepClone(A)
目前情况是:递归没有了,但还是没有破除循环引用。最蛋疼的是还出现了一个新的问题,关系丢失,具体如下
var a = {}
var b = { c: a, d: a}
var e = clone(b)
e.c === e.d // false
然后我们想到,可不可以找一个store做缓存,一来可以打破循环引用,二来 还可以空间换时间 骚秀一波
打开注释,见证奇迹的时刻到了 skr skr skr
骚完之后,我们回归现实 看看这个方法的缺点:
- 如果你不想保持对象的关联关系,不要用此方法
- 如果对象数量很多,不要用此方法
附录
Types方法
var type = (param) => {
var checker = Object.prototype.toString
var getName = () => {
return checker.call(param).slice(8).slice(0, -1).toLowerCase()
}
var is = (typeName) => {
return checker.call(param) === `[object ${typeName}]`
}
var isOneOf = (names = []) => {
return names.some(name => checker.call(param) === `[object ${name}]`)
}
var isEvery = (names = []) => {
return names.every(name => checker.call(param) === `[object ${name}]`)
}
var isNot = (typeNames = []) => {
var checkers = {
string: () => !is(typeNames),
array: () => typeNames.every(name => !is(name))
}
return checkers[getName(typeNames)]()
}
// 暂时闭包够用了,后面用涉及到this了 记得bind
return { is, isOneOf, isEvery, isNot, name: getName() }
}
for-in、Object.keys、Object.getOwnPropertyNames的用法
- for-in:输出原型链+自身所有可枚举的属性
- keys:输出自身可枚举属性,就是for-in + hasOwnProperty
- getOwnPropertyNames:输出自身全部属性
测试深度优先+广度优先
function dataAction(deep, breadth) {
var data = {}, pointer = data
for (var i = 0; i < deep; i++) {
pointer = pointer['data'] = {}
for (var j = 0; j < breadth; j++) {
pointer[j] = j
}
}
return data
}
// test
clone1(dataAction(n,m))
JSON黑科技
// 这个 parse 比较牛逼 可以不丢失function
// 注意:不要搞太大的对象 storage最大5M
const JSONBooster = {
stringify: (toJson) => {
return JSON.stringify(toJson, function (key, val) {
if (typeof val === 'function') { return `0?0:${val}` }
return val
})
},
parse: (str) => {
return JSON.parse(str, function (k, v) {
if (v.indexOf && v.indexOf('function') > -1) {
return evil(`(function(){return ${v} })()`)
}
return v
})
}
}
// eval的代替方案 貌似可行
const evil = (fn) => {
var Fn = Function
return new Fn('return ' + fn)()
}
跪求简历
字节跳动飞书业务内推咯~
在线办公,未来可期!
北京、杭州、武汉、广州、深圳、上海,六大城市等你来投~
感兴趣的朋友可以私我咨询&内推,也可以通过链接直接投递!
海量hc,极速响应~
传送门