最近出现了一个测试环境页面卡死的问题,打开页面一会以后就死掉了,以下图所示:前端
由于一打开页面就卡住了,控制台也卡得比较厉害,动不了,因此不太好调试,在卡死之际Chrome提示是内存超了:vue
怀疑是有个死循环形成的,经过Chrome提供的调用栈发现可能和加入的sentry上报错误插件有关(sentry是一个前端错误追踪上报的库),这个插件会监听Vue.config.errorHandler以上报错误:git
猜想是这个插件引发的,注释掉以后果真好了。github
把测试环境的数据mock到本地复现,通过一番debug,发现缘由是这样的。ajax
sentry会把Vue组件的props取出来进行normalize格式化,以下代码所示:bash
/**
* normalize()
*
* - Creates a copy to prevent original input mutation
* - Skip non-enumerablers
* - Calls `toJSON` if implemented
* - Removes circular references
* - Translates non-serializeable values (undefined/NaN/Functions) to serializable format
* - Translates known global objects/Classes to a string representations
* - Takes care of Error objects serialization
* - Optionally limit depth of final output
*/
export function normalize(input, depth) {
try {
// tslint:disable-next-line:no-unsafe-any
return JSON.parse(JSON.stringify(input, function (key, value) { return walk(key, value, depth); }));
}
catch (_oO) {
return '**non-serializable**';
}
}复制代码
格式化的目的主要是为了上报,以下图所示:函数
在这个格式化里面出了问题,它会递归遍历全部prop的属性,而且会访问有没有toJSON函数:测试
// If value implements `toJSON` method, call it and return early
// tslint:disable:no-unsafe-any
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
return value.toJSON();
}复制代码
若是你把某一个组件的根DOM元素(this.$el)传给了另外一个组件做为prop,这个时候这个元素就会被遍历到,包括这个元素的全部子属性都会被遍历到,因为Vue会给根元素添加一个__vue__属性指向当前组件,以下图所示:ui
致使当前组件的全部属性会被访问到,组件里面又有一个proxy,是vue用来处理template里访问到data里面没写的属性报一个警告:this
当访问它的toJSON属性时就会触发Vue错误:
这个时候又触发了errorHandler,errorHandler里面又访问了toJSON,这样循环触发,就卡死了。
为啥Vue要在DOM节点放一个__vue__属性呢,根据尤大的说法是devtool会使用到:
To some extent yes - the official devtool relies on it too, so it's unlikely to change or break.
固然sentry的设计也有缺陷,应该避免这种循环上报。sentry另一个循环上报的表现是若是它自己的ajax上报挂了,且在须要上报的域名里面没有滤掉它自己上报的域名,就会致使循环上报。
在新版的sentry(5.12,我当前使用的是5.1)里能够看到已经作了修改,加了一个递归深度为3:
到了那个proxy的时候深度变成0了,就不会继续往下走访问到toJSON了:
// If we reach the maximum depth, serialize whatever has left
if (depth === 0) {
return serializeValue(value);
}
// If value implements `toJSON` method, call it and return early
// tslint:disable:no-unsafe-any
if (value !== null && value !== undefined && typeof value.toJSON === 'function') {
return value.toJSON();
}复制代码
进而避免了这种状况。