原文作者:Addy Osmani

譯者:UC 國際研發 Jothy

寫在最前:歡迎你來到「UC國際技術」公眾號,我們將為大家提供與客戶端、服務端、演算法、測試、數據、前端等相關的高質量技術文章,不限於原創與翻譯。

這是一篇關於性能優化的文章,是一篇非常值得你閱讀的文章,文章的內容非常豐富,大概你花 5-10 分鐘閱讀。

在過去的一年裡,我們忙於試圖弄清楚如何讓網路更快、更高效。因此就有了我們希望在本文中與你分享的新工具,方法和庫。在第一部分中,我們將向你展示我們在開發 The Oodles Theater App 時使用的一些優化技術。在第二部分中,我們將討論我們的預測載入實驗和新的 Guess.js 計劃。

注意:你可以在 Youtube 觀看視頻youtube.com/watch?

性能的需要

互聯網每年都在變得越來越重。如果我們檢查網頁的狀態,我們可以看到一個中等大小的移動端頁面大約為 1.5MB,其中大部分是 JavaScript 和圖片。

網站規模不斷擴大,加上其他因素,如網路延遲,CPU 限制,渲染阻塞模式或多餘的第三方代碼,都會導致複雜的性能難題。

大多數用戶把速度當成用戶體驗需要層次理論(User Experience Hierarchy of Needs,見下圖)的最高層。這並不太令人驚訝,因為在頁面載入完成之前,你有很多事情都做不了。你無法從頁面中獲取價值,你無法欣賞它的美學。

圖1. 速度對用戶有多重要?

我們知道性能對用戶很重要,但它又像是一個尋找從哪裡開始優化的祕密。幸好有一些工具可以幫助我們。

Lighthouse ——性能工作流的基礎

Lighthouse 屬於 Chrome DevTools 的一部分,它允許你對你的網站進行審查,並提供優化建議。

我們最近推出了一系列新的性能審查(developers.google.com/w),它們在日常開發工作流中非常有用。

圖2. 新 Lighthouse 審查

讓我們來探索一下如何在一個實際的例子中利用它們:Oodles Theatre App。這是一個小型的 demo web app,你可以在裡面試用我們最愛的一些互動式 Google Doodle,甚至玩一兩局遊戲。

在構建 App 時,我們希望確保它儘可能高效。優化的起點是 Lighthouse 報告。

圖3. Oodles App 的 Lighthouse 報告

我們的 App 在 Lighthouse 報告中的初始表現非常糟糕。使用 3G 網路時,用戶需要等待 15 秒才能獲得第一個有意義的(頁面)繪製,或者(說)讓 App 可交互。 Lighthouse 高亮標識了我們網站的一堆問題,而 23 分的整體性能得分充分反映了這一點。

頁面大小約3.4MB - 我們迫切需要減減肥了。

我們的首次性能挑戰由此開始:找到可以輕鬆刪除的內容,同時不影響整體體驗。

性能優化機會

1. 刪除不必要的資源

有一些顯而易見的東西可以安全刪除掉:空白和評論。

圖4. Minify和壓縮 JavaScript 和 CSS

Lighthouse 在 Unminified CSS & JavaScript 審查中突出了這個機會。程序使用 webpack 進行構建,因此為了縮小體積,我們選擇了 Uglify JS 插件。

縮小體積是一項常見任務,因此你得找到適合你的構建過程的現成解決方案。

該項目中另一個有用的審查是啟用文本壓縮。我們沒有理由發送未壓縮的文件,而且現在大多數 CDN 都開箱即用地支持這個。

我們使用 Firebase Hosting 來託管我們的代碼,Firebase 默認啟用 gzip,因此,通過在合理的 CDN 上託管我們的代碼,我們免費獲得了這個功能。

雖然 gzip 是一種非常流行的壓縮方式,但其他機制如 Zopfli 和 Brotli 也越來越吸引人。 Brotli 受大多數瀏覽器支持,你可以在將資源發送到伺服器之前使用二進位方式對其進行預壓縮。

2. 使用有效的緩存策略

我們的下一步是確保在不必要的情況下我們不會重複發送資源。

Lighthouse 中的低效緩存策略審查使我們注意到,我們可以通過優化緩存策略實現這一目標。通過在我們的伺服器中設置 max-age 過期頭,我們可以確保在重複訪問情況下,用戶可以重用他們之前下載的資源。

理想情況下,你應該在儘可能長的時間內儘可能安全地緩存儘可能多的資源,並提供驗證令牌,以便高效地重新驗證已更新的資源。

3. 刪除未使用的代碼

到目前為止,我們刪除了非必要下載文件的顯要部分,那不太明顯的部分呢?例如,未使用的代碼。

圖5.檢查代碼覆蓋率

有時我們會在 App 中包含不必要的代碼。這種情況尤其是在你的 App 開發了很長一段時間,你的團隊或你的依賴項發生了變化,有時「孤兒庫」被遺忘的情況下。這正是我們經歷過的事情。

最初我們使用 Material Components 庫來快速構建我們的 App 原型。隨著時間的推移,我們轉向了更加定製化的外觀,我們完全忘記了這個庫。幸運的是,代碼覆蓋率檢查幫助我們在 bundle 中重新發現了它。

你可以在 DevTools 中檢查代碼覆蓋率狀態,包括運行時和應用載入時間。你可以在底部截圖中看到兩個大的紅色橫條 - 我們有超過 95% 的 CSS 是未使用的,還有一大堆 JavaScript。

Lighthouse 在未使用的 CSS 規則審查中也提到了這個問題。它表示我們可能節省超過 400kb。所以我們回到代碼裏,刪除了該庫的 JavaScript 和 CSS 部分。

圖6. 棄用 MVC 適配器,我們的樣式下降到了 10KB!

這使我們的 CSS bundle 減少了 20倍,這對於一個很小的,兩行長的 commit 來說太贊了。

當然,它使我們的性能得分上升,而且可交互時間也得到優化。

但是,通過這樣的更改,僅僅檢查指標和分數是不夠的。刪除實際代碼絕不是零風險的,因此你應該始終注意發掘潛在的風險。

我們的代碼在 95% 的代碼中未被使用 - 在某處仍有 5% 在使用。顯然我們的某個組件仍在使用該庫中的樣式 - 塗鴉滑塊中的小箭頭。因為它太小了,我們可以手動將這些樣式合併到按鈕中。

圖7.一個組件仍在使用已刪除的庫

因此,如果您刪除代碼,請確保您擁有適當的測試工作流,以幫助您防範潛在的可見的風險回歸。

4. 避免巨大的網路負載

我們知道大量資源可能會減慢網頁載入速度。他們可能會消耗用戶的錢,他們可能會對他們的數據計劃產生重大影響,因此注意這一點非常重要。

通過使用巨大的網路負載審查,Lighthouse 能夠檢測到我們的某些網路負載存在的問題。

圖8.檢測巨大的網路負載

從這裡可以看到,我們有超過 3mb 的代碼被傳輸 - 這不是一般的多,特別是在移動設備上。

在這個列表的最頂部,Lighthouse 告訴我們有一個2mb大小的未壓縮JavaScript vendor 包。這也是 webpack 高亮顯示的問題。

俗話說:最快的請求是還沒發出的請求。

理想情況下,你應該衡量你發送給用戶的每一項資源的價值,衡量這些資源的性能,並根據初始經驗判斷它是否真的值得被傳輸。因為有時這些資源可以在空閑時間延遲發送,懶載入或處理。

在我們的例子中,因為我們處理的是大量的 JavaScript 包,所以我們很幸運,因為 JavaScript 社區擁有豐富的 JavaScript 包審查工具。

圖9. JavaScript 包審查

我們開始使用 webpack bundle analyzer,它告訴我們,我們有一個名為 unicode 的依賴項,它是 1.6mb 的已解析 JavaScript,非常大的文件。

然後我們轉到我們的編輯器,並使用為可視化代碼準備的 Import Cost 插件,透過它我們能夠看到我們導入的每個模塊的成本。它能幫助我們發現哪個組件包含引用此模塊的代碼。

然後我們切換到另一個工具 BundlePhobia。這個工具允許你輸入任何 NPM 包的名稱,並實際看到它經過壓縮和 gzip 後的預計大小。我們找到了一個很好的替代方案,我們使用的 slug 模塊只有 2.2kb,所以我們用了它。

這對我們的性能產生了很大的影響。在此更改和發現其他減少 JavaScript 包大小的機會之間,我們節省了 2.1mb 的代碼。

綜合考慮到這些 bundle 的壓縮和縮小尺寸,我們總體上看到了 65% 的提升。我們發現這確實值得去做。

因此,總的來說,嘗試去消除你的網站和應用中不必要的下載吧。對資源進行清點並衡量其性能影響,這可能會帶來煥然一新的改變,因此請確保定期審查你的資源。

通過代碼拆分降低 JavaScript 啟動時間

雖然大型網路負載可能對我們的應用程序產生重大影響,但還有另一樣東西也能產生巨大影響——那就是 JavaScript。

JavaScript 是你最昂貴的資產。在移動設備上,如果您發送了大量的 JavaScript,它可能會延遲用戶與界面組件交互的時間。這意味著他們點擊 UI 的操作可能是毫無意義的。因此,瞭解 JavaScript 成本為何如此之高顯得極為重要。

這是瀏覽器處理 JavaScript 的方式。

圖10. JavaScript 處理

我們首先必須下載該腳本,我們的 JavaScript 引擎需要解析該代碼,編譯並執行它。

現在,這些(處理)階段在臺式機或筆記本等高端設備、甚至是高端手機並不用花很長時間。但在中等配置手機上,這個過程可能要花上 5 到 10 倍的時間。這正是延遲交互的原因,因此把它降下來非常關鍵。

為了幫你發現 App 的這些問題,我們向 Lighthouse 引入了一個新的 JavaScript 啟動時間審查工具。

圖11. JavaScript 啟動時間審查

在 Oodle App 的例子中,它顯示我們花了 1.8 秒來啟動 JavaScript。而這段時間所做的,是將所有路由和組件靜態導入到一整個 JavaScript 包中。

解決此問題的方法之一是使用代碼分割。

代碼分割的概念是,不要一次把一整個披薩的 JavaScript 給到你的用戶,不妨一次給只他們所需要的一片?

代碼分割可以應用於路由級別或組件級別。它適用於 React 和 React Loadable,Vue.js,Angular,Polymer,Preact 以及其他多個庫。

我們將代碼分割合併到我們的應用中,從靜態導入切換為動態導入,實現了我們所需的非同步懶載入代碼。

圖13.使用動態導入的代碼分割

這種影響既縮小了 bundle 的大小,也節省了我們的 JavaScript 啟動時間。它把時間降到了 0.78 秒,為應用提速 56%。

通常,如果你正在構建你的 JavaScript 體驗,請務必做到僅向用戶發送他們所需要的代碼。

利用代碼分割等概念,發掘 tree shaking 等思路,查看 webpack-libs-optimizations 倉庫,瞭解在使用 webpack 時如何減小庫的體積的方法。

優化圖片

圖片載入性能笑話

在 Oodle App 中,我們使用了大量圖片。不幸的是,Lighthouse 對它的熱情遠遠低於我們。事實上,我們在所有三個與圖片相關的審查上都掛掉了。

我們忘了對圖片進行優化,沒有正確處理好它們的體積,我們也本可以使用其他圖片格式來優化。

圖14. Lighthouse 圖像審查

我們開始對圖片進行優化。

針對一次性的優化,你可以使用 ImageOptim 或 XNConvert 等可視化工具。

更自動化的方法是使用像 imagemin 這樣的庫,在構建過程中優化圖片。

這樣,你就可以確保將來添加的圖片自動得到優化。一些 CDN,例如 Akamai 或 Cloudinary 或 Fastly 等第三方解決方案,也提供了全面的圖像優化解決方案。所以你也可以放心地把圖片託管到這些服務上。

如果由於成本或延遲問題而不想這麼幹,Thumbor 或 Imageflow 等項目也提供自託管替代方案。

圖15.優化前後

我們的背景 PNG 圖片在 webpack 中被標記為大,事實的確如此。在將調整到 viewport 大小,並通過 ImageOptim 優化後,我們把體積降到了 100kb,還可以接受。

我們對網站上的圖片重複執行了此操作,由此顯著降低了整體頁面的體積。

為動畫內容使用正確的格式

GIF 的代價極其昂貴。令人驚訝的是,GIF 格式從未打算成為動畫平臺。因此,切換到更合適的視頻格式可以大大節省文件大小。

在 Oodle App 中,我們在主頁上使用了 GIF 作為介紹動畫。根據 Lighthouse 的說法,切換到更高效的視頻格式能節省超過 7mb。我們的動畫就差不多 7.3mb 了,這對於任何網站來說都太大了,所以我們把它變成了一個包含兩個源文件的視頻元素——mp4 和 WebM,以兼容更多瀏覽器。

圖16.用視頻替換動畫 GIF

我們使用 FFmpeg 工具將動畫 GIF 轉換為 mp4 文件。 WebM 格式則節省得更多——ImageOptim API 可以做這種轉換。

這種轉換為我們節省了超過 80% 的總體積。這讓我們降到了 1mb 左右。

儘管如此,1mb 仍算是網路傳輸中的大型資源,特別是對於帶寬受限的用戶而言。幸好,我們可以使用 Effective Type API 來檢測到它們處於慢網路,然後給它們提供體積更小的 JPEG。

此介面使用高效的往返時間和降低值來判斷用戶所用的網路類型。它只返回一個字元串,像 slow 2G,2G,3G 或 4G。因此,根據此值,對 4G 以下的用戶,我們使用圖像替換掉了視頻元素。

它確實犧牲了一點點用戶體驗中,但至少在慢網路中,該站點也是可用的了。

懶載入屏幕外圖片

輪播動畫,滑塊或非常長的頁面通常會載入圖像,即使用戶並不能立即在頁面上看到它們。

Lighthouse 將在屏幕外圖像審核中標記此行為,您也可以在 DevTools 的網路面板中自行查看。如果你看到很多圖像傳入,但它們之中只有少數可見,則意味著你可以考慮延遲載入它們。

瀏覽器本身尚不支持懶載入,因此我們必須使用 JavaScript 來添加此功能。我們使用 Lazysizes 庫為我們的 Oodle 封面添加懶載入行為。

Lazysizes 非常智能,因為它不僅可以跟蹤元素的可見性變化,還可以主動預獲取視圖附近的元素,以獲得最佳的用戶體驗。它還提供了 IntersectionObserver 的可選集成,為你帶來非常高效的可見性查找。

在此更改後,我們的圖片將按需提取。如果你想深入瞭解該主題,請查看 images.guide - 一個非常方便和全面的資源。

images.guide: images.guide/

幫助瀏覽器儘早提供關鍵資源

並非每個通過網路發送到瀏覽器的位元組都具有同等的重要性,瀏覽器知道這一點。許多瀏覽器都有試探性方法來決定他們應該首先獲取什麼。所以有時它們會在獲取圖片或腳本之前獲取 CSS。

可能有用的東西是作為頁面作者的我們,可以告訴瀏覽器對我們來說真正重要的是什麼。值得慶幸的是,在過去幾年中,瀏覽器已經添加了許多功能來幫助我們實現這一功能,例如:使用 link rel=preconnect,preload 或 prefetch 實現資源提示。

這些 Web 平臺的功能可以幫助瀏覽器在正確的時間獲取正確的資源,並且它們會比使用腳本完成的一些自定義載入,基於邏輯的方法更有效。

讓我們看看 Lighthouse 如何實際指導我們有效地使用這些功能。

Lighthouse 讓我們做的第一件事就是避免多次與任一源的昂貴往返請求。

圖17.避免多次與任一源的昂貴往返請求

對於 Oodle App,我們實際上重度使用 Google 字體。每當在頁面中使用 Google Fonts 樣式表時,它最多會連接兩個子域。Lighthouse 告訴我們,如果我們能夠預熱這種連接,我們可以在初次連接時節省最多 300 毫秒。

利用 link rel preconnect,我們可以有效地屏蔽連接延遲。

特別是對像 Google Fonts 這種把字體 CSS 託管在 googleapis.com 上,以及把字體資源託管在 Gstatic 上的資源,這可能會產生很大的影響。所以我們應用了這個優化,優化了幾百毫秒。

Lighthouse 建議的下一件事是預先載入關鍵請求。

圖18.預載入關鍵請求

<link rel=preload> 非常強大,它通知瀏覽器當前導航中需要該資源,並且嘗試讓瀏覽器儘快獲取它。

現在,Lighthouse 告訴我們,我們應該預載入我們的關鍵網路字體資源,因為我們正在載入兩種網路字體。

預載入網路字體如下所示 - 指定rel = preload,將字體類型傳入 as 欄位,然後指定要載入的字體類型,例如 woff2。

這對你的頁面產生的影響將非常明顯。

圖19.預載入資源的影響

通常,如果不使用 link rel preload,如果 Web 字體恰好對你的頁面至關重要,那麼瀏覽器必須做的是首先獲取 HTML,解析 CSS,以及接下來的其他資源,最後纔去獲取你的網路字體。

而使用了 link rel preload,一旦瀏覽器解析了 HTML,它就可以提早開始獲取這些網路字體。對我們的 App 來說,這可以減少我們使用 Web 字體渲染文本所花費的時間。

現在,如果你想嘗試使用 Google Fonts 預載入字體,那還沒那麼簡單,我們還有一個問題。

我們在樣式表中的字體中指定的 Google Font URL 恰好是 Google 字體團隊定期更新的內容。這些 URL 可能會過期或定期更新,因此,如果你希望完全控制字體載入體驗,我們建議你自行託管你的 Web 字體。這也很棒,因為它讓你可以訪問 link rel preload 等內容。

在我們的例子中,我們發現 Google Web Fonts Helper 工具在幫助我們離線某些網路字體並在本地設置它們時非常有用,因此請瞭解下該工具。

無論你是將 Web 字體還是 JavaScript 作為關鍵資源的一部分,都應使瀏覽器儘快提供該關鍵資源。

實驗:優先順序提示

今天還有一個特別的東西要與你分享。除了資源提示和預載入等功能外,我們還在開發一種全新的實驗性瀏覽器功能,我們稱之為優先順序提示。

圖20.優先順序提示

這項新功能允許你提示瀏覽器某資源的重要性。它暴露了一個新屬性 - importance - 可取值 low,high 或 auto。

這使我們能降低不太重要資源的優先順序,例如非關鍵樣式,圖片或 fetch API 調用,以減少流量搶佔。我們還可以提升更重要事物的優先順序,例如我們的英雄圖片。

對於我們的 Oodle App,這實際上給了我們一個可以優化的機會

圖21.設置初識可見內容的優先順序

在我們對圖像設置懶載入之前,瀏覽器的做法是,我們將這個圖片輪播與所有塗鴉一起使用,而瀏覽器會在輪播最開始時以高優先順序獲取所有圖像。不幸的是,輪播中間的圖像對用戶來說是最重要的。所以我們的做法是,將背景圖像的重要性設置得非常低,前景圖像的重要性設置得非常高,這在慢速 3G 時能帶來 2 秒的提速,我們也能夠快速地獲取和渲染這些圖像。這是一個很棒的體驗。

我們希望在幾周內將這個功能帶到 Canary,還請密切關注。

制定一個網路字體載入策略

排版是良好設計的基礎,如果你使用的是網路字體,理想情況下你並不想阻塞文本的渲染,而且你絕對不想顯示不可見的文本。

我們在 Lighthouse 中強調了這一點,在 avoid invisible text while web fonts are loading 審查中可以找到。

圖22.在載入網路字體時避免使用不可見文本

如果你使用 font face 代碼塊載入網路字體,並且該字體需要較長時間才能獲取到,你就是在讓瀏覽器決定該做什麼。有些瀏覽器會在退回到系統字體之前等待最多三秒鐘,並且一旦字體下載完畢,瀏覽器最終又會切換到字體。

我們試圖避免這種不可見文本,在這種情況下,如果網路字體載入花了太長時間,我們將無法看到本週的經典塗鴉。幸好,通過一個名為 font-display 的新功能,你實際上可以更好地控制這個過程。

Font display 可幫助你根據交換所需的時間來決定網路字體的渲染或退階方式。

在這種情況下,我們使用字體顯示交換。交換為字體提供零秒塊週期和無限交換週期。這意味著如果字體載入需要一段時間,瀏覽器會立即使用備用字體繪製文本。一旦字體可用,它就會轉換它。

對我們的 App 來說,這功能非常棒,它允許我們很早就顯示一些有意義的文本,並在網路字體準備好後馬上轉換過去。

圖23.字體顯示結果

一般情況下,如果你碰巧在使用網路字體,且它佔據了網路的很大一部分,那麼你得有一個很好的網路字體載入策略。

有許多 Web 平臺能幫你優化字體的載入體驗,你還可以查看 Zach Leatherman 的 Web Font Recipes 倉庫,因為它實在太棒了。

Web Font Recipes repo: zachleat.com/web/recipe

減少渲染阻塞腳本

我們的應用中還有其他部分可以在下載鏈中提前推送,以便提前提供一些基本的用戶體驗。

在 Lighthouse 時間軸上,你可以看到在載入所有資源的前幾秒內,用戶無法看到任何內容。

圖24.減少阻塞式渲染樣式表的機會

下載和處理外部樣式表阻塞了我們的渲染過程的進展。

我們可以嘗試通過先提供一些樣式來優化我們的關鍵渲染路徑。

如果我們提取負責初始渲染的樣式並在 HTML 中內聯它們,瀏覽器可以直接渲染它們而無需等待外部樣式表。

在我們的例子中,我們使用名為 Critical 的 NPM 模塊在構建步驟中內聯 index.html 中的關鍵內容。

雖然這個模塊為我們完成了大部分繁重工作,但要讓它在不同路線上順利運行仍然有點棘手。

如果你不小心或者你的站點結構非常複雜,若你沒有從一開始就規劃 app shell 架構,那麼引入這種模式可能非常困難。

這就是為什麼在早期就考慮性能因素非常重要的原因。如果您從一開始就沒有設計性能,那麼你之後再實施就可能遇到問題。

最終我們的風險得到了回報,我們設法讓它發揮作用,App開始更早地提供內容,顯著改善了我們的第一個有意義的繪畫時間。

結果

這是我們應用在網站上的一長串性能優化。我們來看看結果。結果表明了我們的應用在優化之前和之後,在中等設備、3G 網路上是如何載入的。

Lighthouse 表現得分從 23 上升到 91。在速度方面取得了相當不錯的進步。所有這些變化都是由我們不斷檢查並遵循 Lighthouse 報告推動的。如果你想了解我們如何在技術上實施所有改進,請隨時查看我們的倉庫(github.com/google/oodle),尤其是 PR。

預測性能 - 數據驅動的用戶體驗

我們相信機器學習在未來的許多領域代表著令人興奮的機會。我們希望將來能引發更多實驗的一個想法是,真實數據可以真正指導我們正在創建的用戶體驗。

今天,我們對用戶可能想要或需要的內容做出了很多武斷的決定,由此判斷什麼資源該預先提取,預載入或預先緩存。如果我們猜對了,我們可以優先考慮少量資源,但很難將其擴展到整個網站。

我們實際上有數據可以更好地為我們的優化提供支持。使用 Google Analytics reporting API,我們可以查看下一個首頁以及我們網站上任意網址的退出百分比,從而得出我們應優先考慮哪些資源的結論。

如果我們將其與良好的概率模型相結合,我們就會通過積極預獲取內容來避免浪費用戶的數據。我們可以利用 Google Analytics 數據,並使用機器學習和馬爾可夫鏈或神經網路等模型來實現此類模型。

圖25.用於Web應用程序的數據驅動捆綁

為了促進這個實驗,我們很高興地宣佈一個叫做 Guess.js 的新計劃。

圖26. Guess.js

Guess.js 是一個專註於數據驅動Web用戶體驗的項目。我們希望它能激發人們探索使用數據來改善網路性能並超越它。它是完全開源的,可以 GitHub 上獲取。這是由 Minko Gechev,Gatsby 的 Kyle Matthews,Katie Hempenius 和其他一些人與開源社區合作建立的。

總結

分數和指標有助於提高 Web 的速度,但它們只是手段,而不是目標本身。

我們都經歷過慢網速頁面載入,但我們現在有機會為用戶提供更加愉悅的快速載入體驗。

性能提升是一個旅程。許多小的變化可以帶來巨大的收益。通過使用正確的優化工具並密切關注 Lighthouse 報告,你可以為用戶提供更好、更具包容性的體驗。

特別感謝:Ward Peeters,Minko Gechev,Kyle Mathews,Katie Hempenius,Dom Farolino,Yoav Weiss,Susie Lu,Yusuke Utsunomiya,Tom Ankers,Lighthouse 和 Google Doodles。

英文原文:

developers.google.com/w


「UC國際技術」致力於與你共享高質量的技術文章

歡迎微信搜索 UC國際技術 關注我們的公眾號

或者將文章分享給你的好友~


推薦閱讀:
相關文章