从鸡&蛋问题聊起
鸡生蛋,蛋生鸡。假如鸡可以一直长生不死,蛋都能孵出母鸡,一直如此循环往复。理论上只需一只鸡,就能得到无尽的鸡和蛋。
那么这个现象,如果用JS来表达,是什么样呢,如下图:
-
我们先定义
chicken
和egg
对象; -
chicken
生下一个egg
; -
egg
孵出一只chicken
;
最终我们得到了无穷无尽的鸡和蛋。
为什么要用循环引用
最近在做一个需求,对一个树状结构的数据进行搜索,结果平铺展示。数据的每一个节点状态都能改变,并且这个节点的改变会影响所有的子节点和父节点状态。
数据结构大概如下, 层级不确定:
const tree = [{
id: '0',
children: [
{id: '00'},
{
id: '01',
children: [
{id: '011'},
...
]
},
{id: '02'},
...
]
}]
如果按以往的方法做:
- 将树状数据拍平过程中,添加父级
id
; - 某一节点状态改变,修改子节点状态;
- 遍历整棵树,根据父级
id
找到所有父节点,循环调用方法从内到外一层层更改状态;
明显可以发现,子节点是通过children
字段挂载在当前节点上的,状态容易更改;父节点的话,则要向上查找,过程麻烦的多。
那么,如果我们知道当前节点的父节点的话,是不是容易很多。所以,在数据拍平过程中,给每个节点,都添加一个parent
属性
const treeList = [];
const formatter = (tree, parent=null) => {
tree.forEach(child => {
child.parent = parent;
treeList.push(child);
if (child.children) {
formatter(child.children, child);
}
});
};
formatter(tree);
那么,这样的话,当我们修改了某一个节点的状态,我们可以像获取子节点一样获取父节点,并且通过parent
属性可以一直向上查找出所有的父节点,节省了大量的循环遍历过程。
对象的循环引用会有什么问题
- 对象循环引用会不会带来内存泄漏问题?
如果是采用引用计数法进行垃圾回收,确实会。但是现在标记清除法更加流行,这种垃圾回收机制下,不会造成内存泄漏问题。
JSON.stringify()
方法报错 在对一个循环引用的对象使用JSON.stringify()
方法时会报错,这个确实存在。
如何避免JSON.stringify()
方法报错问题
参考MDN对JSON.stringify的描述:
-
可以给对象添加一个
toJSON
方法;
但此方法只能解决不报错问题,很难得到我们想要的结果。
- 使用
symbol
作为属性键
从描述可以看到,JSON.stringify()
方法会自动忽略对象以symbol作为属性键的属性。所以,如果我们把循环引用的对象,挂载在以symbol
作为属性键的属性上,不就OK了。
- 还可以使用这个JSON库来去除循环引用
代码挺简单的,只有几十行,主要思想是循环遍历,利用WeakMap
来存储每一个对象,记录下对象访问路径的字符串;发现相同的对象,直接替换成访问路径字符串,从而破坏循环引用链路。另外,还提供了恢复的方法。
大家有兴趣可以自己去看看。
tips
对象的循环引用其实在js中很常见,浏览器的event
事件中就有它的身影,event.target.childrenNode
和event.target.parentNode
就可以相互访问,但是JSON.stringify(event.target)
并不会报错,而是返回{}
。
总结
对象的循环引用,在某些时候确实挺好用的,但是使用的时候还是要小心!