问题诱因
在公司的一个项目中,由于要高度自定义导航栏和菜单栏功能,所以想到了使用locaStorage来缓存产生的路由数据,通过该路由数据来动态的展示和修改对应的tag标签和一级菜单栏,不多说,上代码
// 判断当前路由缓存是否和即将跳转的路由名称相同
let flag = false;
// 判断当前路由缓存是否为顶级路由
let flag2 = false;
let routeArr = [];
// 获取本地路由缓存
localStorage.getItem("routeArr")
? (routeArr = JSON.parse(localStorage.getItem("routeArr")))
: (routeArr = []);
let newArr = JSON.parse(JSON.stringify(routeArr));
// 判断是否为相同页面跳转
flag2 = (to.meta.pName != "" && to.meta.pName);
// 判断是否存在
for (let i = 0; i < newArr.length; i++) {
flag = (newArr[i].name == to.name || to.hidden);
}
// 做一次克隆
let obj = lodash.cloneDeep(newArr);
// 存入当前路由本身的父级路由
!flag && flag2
? (() => {
obj.push(lodash.cloneDeep(to.matched[0]));
localStorage.setItem(
"routeArr",
JSON.stringify(lodash.cloneDeep(obj))
);
})()
: 0;
// 存入当前路由本身
!flag && !flag2
? (() => {
obj.push(lodash.cloneDeep(to));
localStorage.setItem(
"routeArr",
JSON.stringify(lodash.cloneDeep(obj))
);
})()
: 0;
因为该段代码没有设计的二级菜单及tag标签。
注:业务需要,需将一级菜单和其自己拆开显示。
后续浏览器报错,提示说循环引用错误,Uncaught(in promise) TypeError: Converting circular structure to JSON,贴图
我在百度过后,提示我说循环引用错误,我愣是没找到到,代码的报错指向的也是
也是这段代码,我想循环引用的错误会不会是由于变量的待用或者是循环使用的方式不规范导致的,
找了许多的解决方案也是没有一个能够真正用的上的,
后来我逐步排查才发现了问题所在
问题排查
在排查代码块的时候我理所当然的想到了会不会是因为路由守卫的回调值内的to值导致的变量循环,于是我对to的原型链注意排查,并未发现有什么可以的地方。
于是我按照常理,先对to变量进行了一次深拷贝,赋值给一个新变量,然后继续运行,发现还是相同的错误,报错指向位置也是相同的,于是误导了我的判断,我认为to是不存在问题的,后来我将代码逐句注释,才在里面找到了罪魁祸首,to变量里的matched属性。
至于我是怎么发现的,中午吃饭的时候突然想到,如果原变量的to做了一次深拷贝赋值给新的变量,那么两个变量的值结构都相同,只是储存位置不同,所以这次深拷贝根本没有作用。
相同的错误可能依旧存在,那么就只有一种可能,to对象没有问题,而是to对象内的属性存在着变量循环的问题,果然在我挨个排查下,结构最复杂的就是其matched属性,我尝试进行深拷贝之后,对新对象的matched属性进行delete,后恢复正常
注意:如果delete matched属性失败,可以尝试单个赋值,即创建一个空对象,然后将值挨个赋值
问题解决
- 尝试单个赋值,即创建一个空对象,然后将值挨个赋值
- 如果未报错,直接尝试使用ES6语法delete to.matched进行删除
- 贴上修改之后的新代码,切逻辑以优化
// 判断当前路由缓存是否和即将跳转的路由名称相同
let flag = false;
// 判断当前路由缓存是否为顶级路由
let flag2 = false;
let routeArr = []
// 获取主路由
localStorage.getItem("routeArr")
? routeArr = JSON.parse(localStorage.getItem("routeArr"))
: 0;
// 判断是子级路由还是父级路由
// (to.meta.pName == to.meta.title) ? flag = false : flag = true
routeArr.map(item => {
if (item.name == to.name) {
flag = true
}
});
flag ? 0 : to.meta.pName == to.meta.title ? flag2 = true : flag2 = false;
if (flag) {
next()
} else {
// 父级路由切换
flag2 ? (() => {
// 注:不要使用深拷贝,采用重新赋值的方法来进行操作,且不要添加to中的matched属性,否则会产生循环引用的问题
let obj2 = {
fullPath: to.fullPath,
hash: to.hash,
meta: JSON.parse(JSON.stringify(to.meta)),
name: to.name,
params: to.params,
path: to.path,
query: to.query,
// 子级路由tag,必须具有一层父级路由信息
childeRouter: [
{
fullPath: to.fullPath,
hash: to.hash,
meta: JSON.parse(JSON.stringify(to.meta)),
name: to.name,
params: to.params,
path: to.path,
query: to.query,
}
]
}
routeArr.push(obj2)
localStorage.setItem("routeArr", JSON.stringify(routeArr));
})() : 0;
// 子级路由切换
!flag2 ? (() => {
// 通过to来获取路由信息,以此判断当前路由的父级路由信息
// 通过to.matched[0]来获取顶级路由信息
// 是否为已存在的子级路由
let flag3 = false
routeArr.map(item => {
if (item.name == to.matched[0].name) {
// 获取到子级路由信息
item.childeRouter.map(item2 => {
if (item2.name == to.name) {
flag3 = true
}
})
if (!flag3) {
item.childeRouter.push({
fullPath: to.fullPath,
hash: to.hash,
meta: JSON.parse(JSON.stringify(to.meta)),
name: to.name,
params: to.params,
path: to.path,
query: to.query,
})
}
}
})
localStorage.setItem("routeArr", JSON.stringify(routeArr));
})() : 0;
// 如果为空,获取当前路由对象中的路由数据
if (routeArr.length == 0) {
router.options.routes.map(item => {
if (item.redirect == "/dashboard") {
routeArr.push({
fullPath: item.redirect,
hash: item.path,
meta: JSON.parse(JSON.stringify(item.meta)),
name: item.name,
params: {},
path: item.path,
query: {},
// 子级路由tag,必须具有一层父级路由信息
childeRouter: [
{
fullPath: item.redirect,
hash: item.path,
meta: JSON.parse(JSON.stringify(item.meta)),
name: item.name,
params: {},
path: item.path,
query: {},
}
]
})
}
})
localStorage.setItem("routeArr", JSON.stringify(routeArr));
}
next()
}
后言
这样就能解决其变量循环的方式,或者你也可以采用将标签路由等信息存储在vue的状态管理器内