一、前言
element-plus el-tree
1.1 循环引用
有两个对象:objA 和 objB,其中 objA 的一个属性指向了对象 objB,而对象 objB 也有一个属性指向了对象 objA,这就造成了循环引用。示例:
const objA = {}
const objB = {}
objA.root = objB
objB.store = objA
console.log('对象间循环引用-objA:', objA)
console.log('对象间循环引用-objB:', objB)
ES6 引入了 Class 概念,类的本质是函数(function),类的实例是对象(object)
cosnt store = new TreeStore()
console.log(typeof TreeStore, typeof store) // funciton object
JSON.stringify() 无法将一个造成循环引用的对象序列化,这种循环引用:无穷无尽,没有尽头。
console.log(JSON.stringify(objA))
报错如下:
二、el-tree
2.1 问题描述
el-tree 中有两个类:TreeStore 和 Node,TreeStore 类存储了树的数据,Node 类生成树节点结构。但是 TreeStore 有一个属性 root,是树的根节点,指向 Node;而树节点 Node 里也有一个属性 store 指向了 TreeStore。 在看这块代码过程中,遇到一个问题:
代码如下:
<template>
<div>
{{root}}
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
import TreeStore from './model/tree-store'
export default defineComponent({
name: 'ElTreeInit',
props: {
data: Array,
nodeKey: String,
props: {
type: Object,
default: () => ({
children: 'children',
label: 'label',
disabled: 'disabled',
}),
},
},
setup(props) {
const store = ref(
new TreeStore({
data: props.data,
key: props.nodeKey,
props: props.props,
})
)
store.value.initialize()
const root = ref(store.value.root)
console.log('props', store.value, root)
return {
root,
}
},
})
</script>
2.2 问题处理
真的,这个问题我找个好久
(1)起初,将 template 里引用 root 的这行代码删掉,报错便没有了,控制台 console.log 的 store 也是正常的,从树根到整棵树的结构都已呈现出来。但是加上这行代码,便产生报错。又仔细看了源码,很困惑。
(2)后来,根据报错的信息去找问题,发现了循环引用。
(3)最后,顺着循环引用,发现循环引用的对象不能通过 JSON.stringfy() 序列化,而 template 里的对象会被执行 JSON.stringfy(),因为无穷无尽没有尽头,所以报错。若是访问非循环引用对象,可避免此报错。
el-tree 源码里并没有在 template 里访问循环引用对象(store.root 和 node.store),在 tree.vue 遍历 root.childNodes 循环调用 tree-node.vue,而 tree-node.vue 循环调用传入的节点 node.childNodes 构造子树。
// tree.vue
<template>
<div>
<!-- {{ root }} -->
<tree-node
v-for="child in root.childNodes"
:key="getNodeKey(child)"
:node="child"
/>
</div>
</template>
// tree-node.vue
<template>
<div>
<div>{{ node.label }}</div>
<div>
<tree-node
v-for="child in node.childNodes"
:key="getNodeKey(child)"
:node="child"
/>
</div>
</div>
</template>
三、实现对循环引用对象的拷贝
额外开辟一块存储空间weakMap(为什么使用WeakMap?因为它相对Map是弱引用),来存储当前对象target和拷贝对象cloneTarget之间的对应关系。当需要拷贝当前对象target时,先去weakMap中找,若找到则直接返回,若没有找到则继续拷贝并且把拷贝结果存储weakMap。
/**
* 判断是否是对象且排除null
* @param {*} target
* @returns
*/
function isObject(target) {
return typeof target === "object" && target !== null
}
/**
* 深拷贝对象
* @param {*} target
* @param {*} weakMap
* @returns
*/
function deepClone(target, weakMap = new WeakMap()) {
if (isObject(target)) {
// ==对象==
let cloneTagret = Array.isArray(target) ? [] : {}
const cache = weakMap.get(target)
if (cache) {
return cache
}
weakMap.set(target, cloneTagret)
for (let curKey in target) {
cloneTagret[curKey] = deepClone(target[curKey], weakMap)
}
return cloneTagret
} else {
// ==基本数据类型==
return target
}
}
let target = {
idCard: 1,
name: "露水晰123",
address: "中国",
}
// ==设置循环引用==
target.parent = target
console.log("深拷贝循环引用对象", deepClone(target))
console.log("深拷贝循环引用对象", deepClone(target) === target) // false
控制台打印结果:
四、总结
- 对象间的循环引用:对象A里的一个属性指向了对象B,而对象B也有一个属性指向对象A,从而造成循环引用,这两个对象也是循环引用对象;
- 类的本质是 funciton,类的实例是 object;
- JSON.stringfy 无法将一个循环引用对象序列化(因为会无穷无尽,没有尽头)。
- WeakMap相对Map式弱引用,弱引用指key若被垃圾机制回收则对应的value也不存在。