新聞中心
我內(nèi)心深處對游戲的熱愛,讓我一直渴望能自己制作一些電子游戲。幾個(gè)月前我開始將這種夢想變?yōu)楝F(xiàn)實(shí),并第一次參加了全球游戲大賽(Global Game Jam)。我和我的團(tuán)隊(duì)使用 Vue.js 構(gòu)建了一個(gè)名為“ZeroDaysLeft”的游戲:

創(chuàng)新互聯(lián)致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營銷,包括成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、SEO優(yōu)化、網(wǎng)絡(luò)推廣、整站優(yōu)化營銷策劃推廣、電子商務(wù)、移動(dòng)互聯(lián)網(wǎng)營銷等。創(chuàng)新互聯(lián)為不同類型的客戶提供良好的互聯(lián)網(wǎng)應(yīng)用定制及解決方案,創(chuàng)新互聯(lián)核心團(tuán)隊(duì)十多年專注互聯(lián)網(wǎng)開發(fā),積累了豐富的網(wǎng)站經(jīng)驗(yàn),為廣大企業(yè)客戶提供一站式企業(yè)網(wǎng)站建設(shè)服務(wù),在網(wǎng)站建設(shè)行業(yè)內(nèi)樹立了良好口碑。
https://zerodaysleft.netlify.com/
其形式是 Web 端的單頁面應(yīng)用程序。這款游戲的主題是環(huán)境保護(hù),我們考慮到商業(yè)活動(dòng)對地球環(huán)境的影響,希望就這個(gè)話題做一些有益的探討。使用 Vue.js 制作的游戲并不多。我的團(tuán)隊(duì)遲到了一天,然后用猜拳的方式選擇了我們要用的框架;我們飛快地寫完了代碼,并在周末結(jié)束時(shí)做出了游戲的可運(yùn)行版本。在本地測試時(shí)一切都很順利。自然,我們?yōu)樽约旱谝淮螌懗鰜淼挠螒蜃髌犯械阶院?,并希望與世界分享它。
可是問題出現(xiàn)了——當(dāng)我們構(gòu)建好應(yīng)用并開始查詢域時(shí),內(nèi)存占用爆表了。它幾乎沒法正常運(yùn)行,不管換什么機(jī)器都會(huì)卡住不動(dòng),即使在強(qiáng)大的基于 Intel i7 處理器的系統(tǒng)上程序也會(huì)崩潰。游戲大賽的時(shí)間限制把我們拉回了現(xiàn)實(shí),我們決定擱置生產(chǎn)性能問題,這樣起碼我們能做出一款能在自己的設(shè)備上運(yùn)行的完整游戲。就像大部分的“已完成”項(xiàng)目一樣,第二天我們就把它拋在腦后了。
但我自己沒法釋懷。它一直困擾著我。問題是出在 Vue.js 上嗎?是 Netlify 嗎?還是因?yàn)槲覀兊娜∏纱a?我必須找出答案。
調(diào)查性能下降的原因
我首先使用 Lighthouse 進(jìn)行了快速測試。所幸 Firefox 為此提供了一個(gè)瀏覽器插件:
https://addons.mozilla.org/en-US/firefox/addon/google-lighthouse/
下面就是我得到的結(jié)果。
89%的數(shù)字挺不錯(cuò)的。實(shí)際上,與許多流行的網(wǎng)站相比,這個(gè)表現(xiàn)相當(dāng)出色。這個(gè)測試指出了一些潛在問題,例如速度指數(shù)和第一次有意義且有內(nèi)容的繪制步驟等。從理論上講,解決這些問題會(huì)進(jìn)一步提高分?jǐn)?shù),但不一定能解決應(yīng)用面臨的嚴(yán)重性能問題。
我們的游戲中有一些圖像和音頻素材資源,但是兩者都不至于讓游戲卡死在那里。我們也可以對這些已經(jīng)優(yōu)化過的資源再過度優(yōu)化一遍,但這可能根本就無濟(jì)于事。
這個(gè)測試無法讓我們真正找出可能導(dǎo)致這一性能問題的原因。于是我開始想:“該不會(huì)是 Vue 的問題吧?”這種想法會(huì)冒出來也沒什么理由,但要是不檢查一下就是蠢了。我檢查了已部署站點(diǎn)的控制臺(tái),結(jié)果空白一片。但警告往往不會(huì)在生產(chǎn)中顯示。當(dāng)我在本地進(jìn)行相同操作時(shí),一堆 Vue 警告讓我吃了一驚。
像大多數(shù)開發(fā)人員一樣,我對控制臺(tái)警告沒那么在意,覺得它們只是警告,而不代表錯(cuò)誤;所以我一般會(huì)把注意力集中在其他地方?;蛟S消除這些警告可以解決我的生產(chǎn)問題,我決定深入研究每個(gè)問題并修復(fù)它們。
所有這些警告均來自我創(chuàng)建的、用來顯示名為 Cards.vue 選項(xiàng)的組件,因此這個(gè)組件可能需要大量重寫。
我決定按順序解決這些控制臺(tái)警告。
- > [Vue warn]: Avoid using non-primitive value as key, use string/number value instead.
- found in
- --->
at src/components/Cards.vue
Vue.js 有很多指令,讓我們能更直觀地使用框架,比如說 v-for 就可以快速將數(shù)組渲染為列表。使用它時(shí),我們需要一個(gè) :key 才能有效地重渲染組件。但我們將一個(gè)對象用作了一個(gè)鍵,這是非原始值,因此導(dǎo)致了這個(gè)錯(cuò)誤。我決定將 index.description 用作一個(gè)新鍵,因?yàn)樗且粋€(gè)字符串,并且在值發(fā)生更改時(shí)可以更好地重新渲染。
- > [Vue warn]: Duplicate keys detected: '[object Object]'. This may cause an update error.
- found in
- --->
at src/components/Cards.vue
將 :key 更改為一個(gè)字符串(index.description)來解決上一個(gè)錯(cuò)誤,就能解決這個(gè)重復(fù)鍵的錯(cuò)誤。我們只能將字符串類型寫入 DOM,因此當(dāng)我們傳遞一個(gè)要渲染的對象時(shí),該對象將轉(zhuǎn)換為等效的字符串(即 [object Object]);并且因?yàn)檫@以前是我們的鍵,所以每個(gè)對象都將轉(zhuǎn)換為 [object Object](除非對象有不同的值),進(jìn)而會(huì)出現(xiàn)重復(fù)鍵警告?,F(xiàn)在既然鍵不是對象,警告就會(huì)消失,效率也會(huì)提升。
- > [Vue warn]: You may have an infinite update loop in a component render function.
- found in
- --->
at src/components/Cards.vue
就一個(gè)非常模糊的警告來說,這個(gè)警告似乎是最重要的:無限循環(huán)意味著內(nèi)存消耗。這條消息并沒有告訴我們可能出了什么問題,但它確實(shí)暗示了問題與組件中的 render 函數(shù)有關(guān)。也許是因?yàn)槲覀儗懙拇a比較取巧,因此觸發(fā)了不間斷的更新,并占用了大量的計(jì)算能力,以至于使瀏覽器和設(shè)備崩潰。
這條警告至少告訴我們要檢查 Cards.vue,所以我的第一個(gè)想法是檢查組件中的反應(yīng)屬性,因?yàn)檫@可能會(huì)導(dǎo)致錯(cuò)誤。反應(yīng)屬性在更改后會(huì)觸發(fā)重新渲染。
我們正在顯示 index.days 和 index.description 中的數(shù)據(jù)。但我們不會(huì)更改這些數(shù)據(jù),我們從 cardInfo 數(shù)組獲得 index。
- > v-for="index in cardInfo.sort(() => Math.random() - 0.7).slice(0,4)"
我們使用這段代碼對數(shù)組中的元素進(jìn)行隨機(jī)排序,然后將前四個(gè)元素顯示為玩家選擇的選項(xiàng)。當(dāng)用戶單擊一個(gè)選項(xiàng)時(shí)將調(diào)用 effects() 函數(shù),它除了會(huì)計(jì)算一個(gè)動(dòng)作如何影響游戲狀態(tài)外,還使用 cardInfo 上的拼接原型刪除前四個(gè)元素。
在 Vue 這種使用虛擬 DOM 的框架里,用上諸如 cardInfo 之類的反應(yīng)屬性后,每當(dāng)數(shù)據(jù)屬性的值更改時(shí)都會(huì)觸發(fā)重新渲染。在我們的應(yīng)用里,我們會(huì)直接使用 sort() 原型來更改它,然后刪除元素來重新排序。所有這些都會(huì)觸發(fā)“無限”的重新渲染,從而引發(fā)警告。
我決定更改數(shù)據(jù)過濾的邏輯,并停止對反應(yīng)屬性 cardInfo 的多次更改。我安裝了 lodash.shuffle 并定義了一個(gè)計(jì)算屬性 shuffledList(),它將創(chuàng)建一個(gè)名為 list 的 cardInfo 副本。我對其應(yīng)用了隨機(jī)排序操作,并返回了一個(gè)“frozen”結(jié)果,然后拆分開來顯示四張卡片。我們使用了 Object.freeze(),它將使我們返回的對象不可變,從而完全停止了所有重新渲染操作。
至此,問題解決了。
掉進(jìn)框架的坑
老實(shí)說,當(dāng)我剛開始調(diào)查性能下降原因的時(shí)候,還覺得我肯定要優(yōu)化很多資源才能解決問題。最后這個(gè)結(jié)果說明,在使用許多框架抽象時(shí)我們都必須非常小心——特別是在 Vue 中更是如此,只有在必要時(shí)才使用某條指令,而且用法一定不能出錯(cuò),因?yàn)樗鼈兘^對有自己的代價(jià)。
這還讓我開始思考自己做過的其他工作,其中應(yīng)用程序可能會(huì)因?yàn)榭蚣芏霈F(xiàn)不必要的性能問題。大多數(shù)現(xiàn)代的前端框架都有很多抽象,使我們能更輕松地為 Web 制作應(yīng)用程序。但我們應(yīng)該牢記一點(diǎn),那就是使用這些東西可能會(huì)引發(fā)潛在的性能問題。
我經(jīng)常使用 Vue.js,所以決定探索一些我以前用過的指令,以前我用這些指令的時(shí)候完全沒考慮過它們可能對應(yīng)用程序帶來的性能影響。其中有三條非常流行的指令進(jìn)入了我的視線。
1. v-if 和 v-show
這兩條指令都是用來有條件地渲染元素的,但是它們背后的工作機(jī)制卻大不相同,因此用法也大相徑庭。v-if 一開始不會(huì)渲染組件,而只在條件為真時(shí)才渲染組件。這意味著當(dāng)你多次切換組件的可見性時(shí),就會(huì)不斷重新渲染。如果你要多次更改組件的可見性,那就不要使用這個(gè)功能。這會(huì)影響你的性能。
v-show 是一個(gè)很好的替代品。不管你是否啟用 CSS 都會(huì)渲染你的組件,但是只會(huì)根據(jù)條件是 true 還是 false 來決定組件是否可見。這種方法確實(shí)有其缺點(diǎn),因?yàn)樗粫?huì)將非必要組件的渲染推遲到你需要它們在屏幕上實(shí)際出現(xiàn)的時(shí)候。如果你的初始渲染沒那么復(fù)雜,那么它就很合適。
2. v-for
這條指令通常用來從數(shù)組中渲染列表。它有一個(gè)特殊的語法,形式為 item in list,其中 list 是源數(shù)據(jù)數(shù)組,而 item 是要迭代的數(shù)組元素的別名。默認(rèn)情況下,Vue 在源數(shù)據(jù)數(shù)組上添加 watchers,每當(dāng)發(fā)生更改時(shí)它就會(huì)觸發(fā)重新渲染。這種持續(xù)的重新渲染可能會(huì)對應(yīng)用程序性能產(chǎn)生不利影響。如果你只想可視化對象,那么 Object.freeze() 是一個(gè)很好的解決方案,可以大大提高性能。但是請務(wù)必記住,你將無法更新組件或編輯對象數(shù)據(jù)。
在這個(gè)研究過程中我還意識(shí)到,Lighthouse 可能檢查的是以更直接的方式影響用戶體驗(yàn)的應(yīng)用性能指標(biāo),所以接下來我的疑問就是如何跟蹤服務(wù)器上的應(yīng)用程序性能。
我們是不是太依賴直覺,是不是在假設(shè)開發(fā)人員知道自己在做什么,假設(shè)他們遵循的是最佳實(shí)踐?不管怎樣,這次經(jīng)歷讓我對單頁應(yīng)用程序的性能產(chǎn)生了不同的看法。大家可以在 GitHub 上查看上述項(xiàng)目的存儲(chǔ)庫:
https://github.com/Maria218/GlobalGameJamThing
本文標(biāo)題:我發(fā)現(xiàn)了Vue.js中的性能陷阱
URL分享:http://fisionsoft.com.cn/article/dhhcigg.html


咨詢
建站咨詢
