記 Vue 大型表單項目的一個性能問題

問題場景

身為一個表單表格工程師呵晚,自然日復(fù)一日的寫著表單表格坦袍,本以為已經(jīng)沒啥難點的時候轉(zhuǎn)眼間就來了一個有意思的情況弧岳,在超大量 數(shù)據(jù)綁定在 vue 的時候出現(xiàn)了表單操作起來卡頓的情況。

這里先貼上本項目出現(xiàn)的情況演示的 github 上的地址,tag1.0.1(https://github.com/everlose/more-form-demo/tree/v1.0.1)

如圖所見酱酬,當(dāng)在 input 輸入數(shù)據(jù)的時候壹无,連續(xù)輸入會感覺明顯的延遲。

那么,這到底是怎么回事勺届?

代碼

上述的表單數(shù)據(jù)項修改頻繁由后端返回,于是在前端需要渲染從后端返回的 68kb 的一個 JSON 數(shù)據(jù)串娶耍,包括所有配置表單項以及其可能的選項值免姿,數(shù)據(jù)見這里

核心渲染是有這么一段

??

????{{config.title}}

????


????????????class="basic-form-item"

????????????v-for="(item, itemIndex) in config.formItems"

????????????:key="itemIndex"

????????????:prop="item.code"

????????????:label="item.name"

????????????:required="item.required"

????????????:rules="item.rules">


????????????????v-if="item.type === 'radio'"

????????????????v-model="formData[item.code]">


????????????????????v-for="(option, radioIndex) in formOptions[item.optionCode]"

????????????????????:key="option.value"

????????????????????:label="option.value"

????????????????????:disabled="item.disabled">

????????????????????{{?option.label?}}




????????????????v-else-if="item.type === 'input'"

????????????????:class="{ longInput: item.isLongInput }"

????????????????:placeholder="item.placeholder || '請輸入'"

????????????????v-model="formData[item.code]"

????????????????:label="item.label"

????????????????:disabled="item.disabled"

????????????????:maxlength="item.maxLength">



????????????????v-else-if="item.type === 'select'"

????????????????v-model="formData[item.code]"

????????????????:disabled="item.disabled"

????????????????:placeholder="item.placeholder || '請選擇'">


????????????????????v-for="(option, optionsIndex) in formOptions[item.optionCode]"

????????????????????:key="option.value"

????????????????????:label="option.label"

????????????????????:value="option.value">





這就是一個簡單的雙層遍歷渲染所有表單配置項的模版代碼,其中的 formConfig 正是所有配置表單項榕酒,數(shù)據(jù)量極多胚膊。formOptions 掛載了所有表單選項值,也是動輒幾千項想鹰。

思路

正當(dāng)我對著這么高的操作延時發(fā)愁的時候紊婉,組里一個大佬提醒我,可能是 Vue.prototype._update 這個觸發(fā)的太頻繁了辑舷。

我急忙找到這一段打了個斷點調(diào)試

Vue.prototype._update 這函數(shù)里觸發(fā)的是 VNode 虛擬節(jié)點的比對更新喻犁,打斷點調(diào)試后發(fā)現(xiàn)實際上這是一個循環(huán),在控制臺里輸出 this.$el 的時候能得到正在深度遍歷中的節(jié)點何缓,沿著根結(jié)點 App(也是 formConfig 數(shù)據(jù)綁定的作用域) 開始直到具體觸發(fā)輸入的那個表單元素株汉。

在本項目里是使用了遍歷輸出所有的表單元素,并且當(dāng)前組件的作用域是直接掛在根結(jié)點上的歌殃,是否就是這個遍歷引發(fā)了如此高的延時呢?于是我找到上圖右側(cè)的調(diào)用堆棧蝙云,發(fā)現(xiàn)正是 flushSchedulerQueue 函數(shù)寫著一個 for 循環(huán)氓皱。

在 flushSchedulerQueue 函數(shù)中的 for 循環(huán)里頭尾插入代碼來獲取耗費時間。

結(jié)果得知輸入時的延遲大概在 300ms 之上勃刨。

似乎問題就找到了波材,flushSchedulerQueue 函數(shù)針對 data 中數(shù)據(jù)的修改把 watcher 推送進隊列里在更新,這一循環(huán)耗費的時間比較長身隐。

解決

其實早在調(diào)試 Vue.prototype._update 函數(shù)就初見端倪廷区,循環(huán)中的 this.$el 從當(dāng)前組件的根部開始深度遍歷,遍歷了太多次贾铝,那么只要想辦法縮小當(dāng)前組件所綁定的數(shù)據(jù)量就解決了隙轻。

于是核心代碼調(diào)整為

??

? ? ??

只是用一個 edit-form 包裹剛剛所有的 el-form-item 的渲染代碼就解決了,再次調(diào)試 Vue.prototype._update 得出遍歷節(jié)點 this.$el 已經(jīng)變?yōu)橄聢D所示的 div.edit-form 了垢揩,flushSchedulerQueue 函數(shù) for 循環(huán)的延遲也變?yōu)?10ms 左右

修復(fù)版的代碼在2.0.0的tag上玖绿,這里貼上鏈接(https://github.com/everlose/more-form-demo/tree/v2.0.0)

后記

本質(zhì)上這就是一個原則,最好不要在一個vue組件上直接綁定如此多的數(shù)據(jù)叁巨,如果有大量數(shù)據(jù)請分多個組件綁定斑匪。這么淺嘗輒止實在讓人不夠盡興,于是這里貼上 Vue.prototype._update 前的關(guān)鍵部分調(diào)用堆棧以及其函數(shù)作用锋勺。

找到項目中 node_modules 下的 vue.esm.js

# 往input里輸入將會觸發(fā)model data的更新

978?set:?function?reactiveSetter?(newVal)??

# 訂閱器dep是數(shù)據(jù)綁定和視圖更新的關(guān)鍵蚀瘸,這里觸發(fā)去通知相關(guān)視圖的更新

994?dep.notify();??

673?Dep.prototype.notify??

# notify函數(shù)里的subs實際上是Watcher對象的實例狡蝶,這里觸發(fā)視圖更新操作

677?subs[i].update();?subs實際上是包裹watcher的數(shù)組??

3093?Watcher.prototype.update??

# 把watcher塞進一個隊列里,這里是和異步更新視圖有關(guān)贮勃。

3100?queueWatcher(this);??

2945?function?queueWatcher?(watcher)?push到隊列里??

# nextTick是具體做異步更新的部分

2963?nextTick(flushSchedulerQueue);??

1778?function?nextTick?(cb,?ctx)??

# 異步操作實際上是原生 H5 MessageChannel API 通道通信來推送消息來實現(xiàn)變化贪惹。

1738?port.postMessage(1);


# 注意在異步操作中,最終傳入的回調(diào)函數(shù)被執(zhí)行來進行下面視圖的更新衙猪。這里是執(zhí)行一個任務(wù)調(diào)度隊列的調(diào)度過程馍乙,需要循環(huán)遍歷。

2856?function?flushSchedulerQueue??

3108?Watcher.prototype.run??

# Evaluate the getter, and re-collect dependencies.

3043?Watcher.prototype.get??

# watcher中的getter的name就叫updateComponent垫释,于是被執(zhí)行

2689?updateComponent??

2690?vm._update(vm._render(),?hydrating);??

# 進入vue的生命周期中的update函數(shù)

2548?Vue.prototype._update??

# patch做的是vnode的節(jié)點比對丝格,最終把新的vnode結(jié)構(gòu)渲染到具體視圖,不再多做描述棵譬。

2572?vm.$el?=?vm.__patch__(prevVnode,?vnode);??

貼上提供思路的大佬的github地址: https://github.com/answershuto

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末显蝌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子订咸,更是在濱河造成了極大的恐慌曼尊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脏嚷,死亡現(xiàn)場離奇詭異骆撇,居然都是意外死亡,警方通過查閱死者的電腦和手機父叙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門神郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趾唱,你說我怎么就攤上這事涌乳。” “怎么了甜癞?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵夕晓,是天一觀的道長。 經(jīng)常有香客問我悠咱,道長蒸辆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任乔煞,我火速辦了婚禮吁朦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渡贾。我一直安慰自己逗宜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纺讲,像睡著了一般擂仍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熬甚,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天逢渔,我揣著相機與錄音,去河邊找鬼乡括。 笑死肃廓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诲泌。 我是一名探鬼主播盲赊,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼敷扫!你這毒婦竟也來了哀蘑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤葵第,失蹤者是張志新(化名)和其女友劉穎绘迁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卒密,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡缀台,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哮奇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片将硝。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屏镊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痰腮,我是刑警寧澤而芥,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站膀值,受9級特大地震影響棍丐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沧踏,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一歌逢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翘狱,春花似錦秘案、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赚导。三九已至,卻和暖如春赤惊,著一層夾襖步出監(jiān)牢的瞬間吼旧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工未舟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留圈暗,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓裕膀,卻偏偏與公主長得像员串,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魂角,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 問題場景 身為一個表單表格工程師昵济,自然日復(fù)一日的寫著表單表格,本以為已經(jīng)沒啥難點的時候轉(zhuǎn)眼間就來了一個有意思的情況...
    一個敲代碼的前端妹子閱讀 2,936評論 3 9
  • 問題場景 身為一個表單表格工程師野揪,自然日復(fù)一日的寫著表單表格访忿,本以為已經(jīng)沒啥難點的時候轉(zhuǎn)眼間就來了一個有意思的情況...
    深海鯽魚堡閱讀 378評論 1 0
  • 1.安裝 可以簡單地在頁面引入Vue.js作為獨立版本,Vue即被注冊為全局變量斯稳,可以在頁面使用了海铆。 如果希望搭建...
    Awey閱讀 10,995評論 4 129
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容挣惰。關(guān)于...
    云之外閱讀 5,045評論 0 29
  • 2017年很快就走到了最后一月卧斟,然而自己的年目標(biāo)實現(xiàn)了嗎?這個問題很難回答憎茂,因為連自己的年目標(biāo)是什么都記不清...
    木石_云水閱讀 212評論 0 1