一個element.__vue__引起的饅頭

最近出現了一個測試環境頁面卡死的問題,打開頁面一會以後就死掉了,以下圖所示:前端

由於一打開頁面就卡住了,控制檯也卡得比較厲害,動不了,因此不太好調試,在卡死之際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();
    }複製代碼

進而避免了這種狀況。

相關文章
相關標籤/搜索