1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        你知道的前端優(yōu)化手段

        共 12312字,需瀏覽 25分鐘

         ·

        2021-06-11 12:45


        點擊上方 前端瓶子君,關注公眾號

        回復算法,加入前端編程面試算法每日一題群

        來源:HuberTRoy

        https://juejin.cn/post/6966857691381645325

        會優(yōu)化,我就不是不優(yōu),就是看著慢,哎~,就是玩兒~。

        banner.png

        前言

        性能優(yōu)化是一個項目發(fā)展到一定時期之后繞不開的話題,也是每個工程師心中永遠在撩撥的刺。

        總結一下常用的前端性能優(yōu)化的方法,希望對大家有些幫助~。

        性能可能帶來的影響(販賣焦慮警告??)

        試想當你做的酷炫特效因為慢了0.1秒就少被一個人看到時的落寞(ㄒoㄒ),

        試想當你引以為傲的細節(jié)交互因為慢了0.2秒就被競爭對手的平庸互動拉走用戶的氣憤(╯>д<)╯?˙3˙?,

        試想當你精心打造的漂亮頁面因為慢了0.3秒就被搜索引擎無情的排在后面的無奈∑(O_O;)。

        所以,是時候重拳出擊了,重鑄性能的榮光,我輩義不容辭( ̄▽ ̄)/。

        調(diào)試工具

        Network面板

        Network面板記錄了與服務器交互的具體細節(jié)。

        在這里我們可以看到發(fā)起的請求數(shù)量,傳輸體積以及解壓縮后的體積,同時還可以知道哪些資源是命中了強緩存,哪些資源命中的協(xié)商緩存。

        network2.jpg

        查看某一個請求的瀑布流可以讓我們清晰的看到一個資源從服務器到達我們的電腦所花的時間。

        如上圖,排隊用了1.65ms,DNS查詢用了21.47ms,initial connection(進行TCP握手的時間)用了56.25ms,SSL握手的時間用了37.87ms,然后又用了100多ms第一個字節(jié)到達我們的電腦(TTFB - 上面的查詢/建立),接收整個文檔花了17ms。

        這時候我們基于上面的信息就可以粗略的得到,如果能在請求資源之前如果已經(jīng)得到DNS地址(預查詢)可以省去21ms,已經(jīng)進行過握手可以省去100ms(預連接),如果干脆請求也不請求可以省去200ms(緩存)繼而針對這些點做對應的策略。

        Network面板可以讓我們初步評估網(wǎng)站性能,對網(wǎng)站整體的體積,網(wǎng)絡的影響帶來一個整體的認知,同時提供一些輔助功能,如禁用緩存,block某些資源。

        lighthouse面板

        lighthouse1.jpg

        lighthouse是對網(wǎng)站整體的評估,通過幾個不同的指標給網(wǎng)站進行打分。

        First Contentful Paint 首屏渲染時間,Chrome會取第一個渲染出來的元素作為時間參考。

        Time to Interactive 可交互時間,從能看到能摸的時間點。

        Speed Index 速度指數(shù),頁面的填充速度。

        Total Blocking Time 從能看到能摸之間超過50ms的任務總和。

        Largest Contentful Paint 頁面中最大的那塊渲染的時間點。

        Cumulative Layout Shift 元素移動所累積的時間點,比如有一個absolute的元素突然從左邊移到了右邊。

        同時針對網(wǎng)站的信息,lighthouse還會給出一些完善建議:

        lighthouse2.jpg

        這些建議可以幫助我們在接下來的優(yōu)化中提供一個大致的方向。

        performance面板

        performance1.jpg

        performance面板會給我們提供一個具體的執(zhí)行過程,從HTML文檔下載,解析HTML,到解析CSS,計算樣式,執(zhí)行JS。

        火焰圖

        performance2.jpg

        從火焰圖我們可以找到長任務,分析長任務,或者找到某些無關緊要的任務把他們拆分,延后,優(yōu)化使他們達到一個理想狀態(tài)。

        performance monitor面板

        pm1.jpg

        performance monitor讓我們監(jiān)控內(nèi)存和CPU的占用,它給出的是整體的占用數(shù)據(jù),可以用來觀察某一段代碼某一個特效會不會造成性能影響。

        webpack-bundle-analyze

        wba.gif

        如果你用到了webpack打包,可以用它來分析打包后的文件,做成具體策略。

        從輸入一個URL談起

        這是一個URL為了見到你穿越無數(shù)路由器的感人故事。

        DNS查詢

        與服務器交互首先要進行DNS查詢,得到服務器的IP地址,瀏覽器會首先查詢自己的緩存,之后會查詢本地HOSTS,如果仍然沒找到會發(fā)起向DNS服務器查詢的請求。

        在這里我們可以做的優(yōu)化不多,DNS是我們相對不可控的一個條件,但我們?nèi)匀豢梢宰龅囊粋€優(yōu)化策略是預查詢。

        進行DNS預查詢

        在文檔頂部我們可以將我們即將要請求的地址的DNS預先查詢,通過插入一個link標簽

        <link rel="dns-prefetch" >

        來告知瀏覽器我們將要從這個地址(通常會是存放靜態(tài)資源的CDN的地址,)拉取數(shù)據(jù)了,你先查詢一下,當用到的時候就可以直接拿到對應的IP。

        dns-prefetch

        建立HTTP(TCP)連接

        得到服務器IP之后,首先進行三次握手,之后會進行SSL握手(HTTPS),SSL握手時會向服務器端確認HTTP的版本。

        針對這方面的優(yōu)化,前端可做的事情不多,主要是服務器端的事情,不過仍然要了解一下前端可以看得到的策略。

        keep-alive

        由于TCP的可靠性,每條獨立的TCP連接都會進行一次三次握手,從上面的Network的分析中可以得到握手往往會消耗大部分時間,真正的數(shù)據(jù)傳輸反而會少一些(當然取決于內(nèi)容多少)。HTTP1.0和HTTP1.1為了解決這個問題在header中加入了Connection: Keep-Alive,keep-alive的連接會保持一段時間不斷開,后續(xù)的請求都會復用這一條TCP,不過由于管道化的原因也會發(fā)生隊頭阻塞的問題。

        HTTP1.1默認開啟Keep-Alive,HTTP1.0可能現(xiàn)在不多見了,如果你還在用,可以升級一下版本,或者帶上這個header。

        connection keep-alive

        HTTP2

        HTTP2相對于HTTP1.1的一個主要升級是多路復用,多路復用通過更小的二進制幀構成多條數(shù)據(jù)流,交錯的請求和響應可以并行傳輸而不被阻塞,這樣就解決了HTTP1.1時復用會產(chǎn)生的隊頭阻塞的問題,同時HTTP2有首部壓縮的功能,如果兩個請求首部(headers)相同,那么會省去這一部分,只傳輸不同的首部字段,進一步減少請求的體積。

        Nginx開啟HTTP2的方式特別容易,只需要加一句http2既可開啟:

        server {
         listen 443 ssl http2; # 加一句 http2.
         server_name domain.com;
        }
        復制代碼

        成本低廉,效果巨大。

        HTTP2

        緩存

        緩存通過復用之前的獲取過的資源,可以顯著提高網(wǎng)站和應用程序的性能,合理的緩存不僅可以節(jié)省巨大的流量也會讓用戶二次進入時身心愉悅,如果一個資源完全走了本地緩存,那么就可以節(jié)省下整個與服務器交互的時間,如果整個網(wǎng)站的內(nèi)容都被緩存在本地,那即使離線也可以繼續(xù)訪問(很酷,但還沒有完全很酷)。

        HTTP緩存主要分為兩種,一種是強緩存,另一種是協(xié)商緩存,都通過Headers控制。

        整體流程如下:

        cache.png

        強緩存

        強緩存根據(jù)請求頭的ExpiresCache-Control判斷是否命中強緩存,命中強緩存的資源直接從本地加載,不會發(fā)起任何網(wǎng)絡請求。

        Cache-Control的值有很多:

        Cache-Control: max-age=<seconds>
        Cache-Control: max-stale[=<seconds>]
        Cache-Control: min-fresh=<seconds>
        Cache-control: no-cache
        Cache-control: no-store
        Cache-control: no-transform
        Cache-control: only-if-cached
        復制代碼

        常用的有max-ageno-cacheno-store。

        max-age 是資源從響應開始計時的最大新鮮時間,一般響應中還會出現(xiàn)age標明這個資源當前的新鮮程度。

        no-cache 會讓瀏覽器緩存這個文件到本地但是不用,Network中disable-cache勾中的話就會在請求時帶上這個haader,會在下一次新鮮度驗證通過后使用這個緩存。

        no-store 會完全放棄緩存這個文件。

        服務器響應時的Cache-Control略有不同,其中有兩個需要注意下:

        1. public, public 表明這個請求可以被任何對象緩存,代理/CDN等中間商。
        2. private,private 表明這個請求只能被終端緩存,不允許代理或者CDN等中間商緩存。

        Expires是一個具體的日期,到了那個日期就會讓這個緩存失活,優(yōu)先級較低,存在max-age的情況下會被忽略,和本地時間綁定,修改本地時間可以繞過。

        另外,如果你的服務器的返回內(nèi)容中不存在ExpiresCache-Control: max-age,或 Cache-Control:s-maxage但是存在Last-Modified時,那么瀏覽器默認會采用一個啟發(fā)式的算法,即啟發(fā)式緩存。通常會取響應頭的Date_value \- Last-Modified_value值的10%作為緩存時間,之后瀏覽器仍然會按強緩存來對待這個資源一段時間,如果你不想要緩存的話務必確保有no-cacheno-store在響應頭中。

        協(xié)商緩存

        協(xié)商緩存一般會在強緩存新鮮度過期后發(fā)起,向服務器確認是否需要更新本地的緩存文件,如果不需要更新,服務器會返回304否則會重新返回整個文件。

        服務器響應中會攜帶ETagLast-Modified,Last-Modified 表示本地文件最后修改日期,瀏覽器會在request header加上If-Modified-Since(上次返回的Last-Modified的值),詢問服務器在該日期后資源是否有更新,有更新的話就會將新的資源發(fā)送回來。

        但是如果在本地打開緩存文件,就會造成Last-Modified被修改,所以在HTTP / 1.1 出現(xiàn)了ETag。

        Etag就像一個指紋,資源變化都會導致ETag變化,跟最后修改時間沒有關系,ETag可以保證每一個資源是唯一的

        If-None-Match的header會將上次返回的ETag發(fā)送給服務器,詢問該資源的ETag是否有更新,有變動就會發(fā)送新的資源回來

        ETag(If-None-Match)的優(yōu)先級高于Last-Modified(If-Modified-Since),優(yōu)先使用ETag進行確認。

        協(xié)商緩存比強緩存稍慢,因為還是會發(fā)送請求到服務器進行確認。

        CDN

        CDN會把源站的資源緩存到CDN服務器,當用戶訪問的時候就會從最近的CDN服務器拿取資源而不是從源站拿取,這樣做的好處是分散了壓力,同時也會提升返回訪問速度和穩(wěn)定性。

        壓縮

        合理的壓縮資源可以有效減少傳輸體積,減少傳輸體積的結果就是用戶更快的拿到資源開始解析。

        壓縮在各個階段都會出現(xiàn),比如上面提到的HTTP2的首部壓縮,進行到這一步的壓縮是指對整個資源文件進行的壓縮。

        瀏覽器在發(fā)起請求時會在headers中攜帶accept-encoding: gzip, deflate, br,告知服務器客戶端可以接受的壓縮算法,之后響應資源會在響應頭中攜帶content-encoding: gzip告知本文件的壓縮算法。

        GZIP壓縮

        GZIP是非常常用的壓縮算法,現(xiàn)代客戶端都會支持,你可以在上傳文件時就上傳一份壓縮后的文件,也可以讓Nginx動態(tài)壓縮。

        進行頁面渲染

        關鍵渲染路徑

        workflow.jpg

        關鍵渲染路徑是瀏覽器將HTML/CSS/JS轉(zhuǎn)換為屏幕上看到的像素內(nèi)容所經(jīng)過的一系列步驟。

        瀏覽器得到HTML后會開始解析DOM樹,CSS資源的下載不會阻塞解析DOM,但是也要注意,如果CSS未下載解析完成是會阻塞最終渲染的。

        從Performance面板中可以清晰的看到瀏覽器如何解析HTML的:

        parsehtml.jpg

        得到HTML后首先會解析HTML,然后解析樣式,計算樣式,繪制圖層等等操作,JS腳本運行,之后可能會重復這一步驟。

        parsehtml2.jpg

        在這里前端可以做的事情多了起來,接下來自頂向下說起。

        渲染頁面

        預加載/預連接內(nèi)容

        和前面說的DNS預查詢一樣,可以將即將要用到的資源或者即將要握手的地址提前告知瀏覽器讓瀏覽器利用還在解析HTML計算樣式的時間去提前準備好。

        preload

        使用link的preload屬性預加載一個資源。

        <link rel="preload" href="style.css" as="style">
        復制代碼

        as屬性可以指定預加載的類型,除了style還支持很多類型,常用的一般是stylescript,css和js。

        其他的類型可以查看文檔:

        preload

        prefetch

        prefetch和preload差不多,prefetch是一個低優(yōu)先級的獲取,通常用在這個資源可能會在用戶接下來訪問的頁面中出現(xiàn)的時候。

        當然對當前頁面的要用preload,不要用prefetch,可以用到的一個場景是在用戶鼠標移入a標簽時進行一個prefetch。

        prefetch

        preconnect

        preconnect和dns-prefetch做的事情類似,提前進行TCP,SSL握手,省去這一部分時間,基于HTTP1.1(keep-alive)和HTTP2(多路復用)的特性,都會在同一個TCP鏈接內(nèi)完成接下來的傳輸任務。

        script加標記

        當瀏覽器解析至script標簽時,瀏覽器的主線程就會等待script,或者運行script,然后繼續(xù)開始構建,在以前,如果你把script標簽放到了文檔的最上面,那么在等待下載和運行的這段時間內(nèi)頁面就會處于白屏和無法操作的狀態(tài),并且不是并行的下載,瀏覽器會逐個下載并運行,這是一個相當糟糕的體驗。所以都會選擇將script放在文檔底部,盡可能推后腳本的執(zhí)行時機,不過并不完全可控。

        時至今日,我們可以給script標簽增加標記,使其異步(延遲)運行,把可控權交給開發(fā)者。

        async標記

        <script src="main.js" async>

        async標記告訴瀏覽器在等待js下載期間可以去干其他事,當js下載完成后會立即(盡快)執(zhí)行,多條js可以并行下載。

        async的好處是讓多條js不會互相等待,下載期間瀏覽器會去干其他事(繼續(xù)解析HTML等),異步下載,異步執(zhí)行。

        defer標記

        <script src="main.js" defer></script>

        與async一樣,defer標記告訴瀏覽器在等待js下載期間可以去干其他事,多條js可以并行下載,不過當js下載完成之后不會立即執(zhí)行,而是會等待解析完整個HTML之后在開始執(zhí)行,而且多條defer標記的js會按照順序執(zhí)行,

        <script src="main.js" defer></script>
        <script src="main2.js" defer></
        script>
        復制代碼

        即使main2.js先于main.js下載完成也會等待main.js執(zhí)行完后再執(zhí)行。

        到底該用哪個標記

        兩個標記都是為了讓script標簽實現(xiàn)異步下載,主要的區(qū)別在于async無法保證順序且下載完就會執(zhí)行而defer則會等待整個HTML解析之后才會開始執(zhí)行,并且按照插入的順序執(zhí)行。

        如果兩個script之間沒有依賴關系并且可以盡快執(zhí)行的更加適合使用async,反之如果兩個script之間有依賴關系,或者希望優(yōu)先解析HTML,則defer更加適合。

        視窗外的內(nèi)容懶加載

        懶加載也是一個經(jīng)常被提及的技術,視窗外的內(nèi)容是不會被用戶立即看到的,這時加載過多的內(nèi)容反而拖慢了網(wǎng)站整體的渲染,我們就可以用懶加載推遲這部分內(nèi)容的加載來達到加速可訪問和可交互性的目的,等用戶即將到達視窗內(nèi)的時候再開始加載這部分內(nèi)容,通常懶加載會與loading和骨架屏等技術搭配使用。

        減少無意義的回流

        回流與重繪是一個老生常談的問題,當瀏覽器大小改變/滾動,DOM增刪,元素尺寸或者位置發(fā)生改變時都會發(fā)生回流,回流意味著瀏覽器要重新計算當前頁面的與之相關的所有元素,重新進行整體的布局。

        reflow.jpg

        這是一個非常消耗性能的事情,有些情況下回流無法避免,有些情況下則可以省略無意義的回流,比如用Js將20個li更改到同樣的尺寸時避免將每個li都即時更改,應該用class一次性更改。

        圖片視頻選擇合理的尺寸

        分辨率越高的圖片顯示出來越消耗性能,當然帶來的好處是更加的清晰,但很多情況下清晰并不是一個特別重要的標準,我們可以犧牲一部分清晰度來讓圖片視頻體積更小,通常PC使用1倍圖,移動端使用2倍圖就夠了,原圖可以結合懶加載等待空閑或者主動觸發(fā)時在加載,像是微信QQ等聊天時發(fā)的表情包一樣,都是點開才會加載原圖。

        這往往是一個容易被忽略(可能因為感覺沒必要)提升又很大的事情,如果你的網(wǎng)站圖片很多強烈建議著手優(yōu)化。

        選擇一個支持動態(tài)剪裁的云服務即可享受這份美好~。

        寫代碼時可以做的事

        上面從代碼寫完的角度談起,接下來從寫代碼的角度談起。

        首先是打包。

        Tree-shaking

        Tree-shaking指的是消除沒被引用的模塊代碼,減少代碼體積大小,以提高頁面的性能,最初由rollup提出。

        webpack2加入對Tree-shaking的支持,webpack4中Tree-shaking默認開啟,Tree-shaking基于ESModule靜態(tài)編譯而成,所以如果想要生效,在寫代碼的時候注意不要用CommonJS的模塊,同時也要注意不要讓babel給編譯成CommonJS的形式。

        Tree-shaking連帶的有一個sideEffects的概念,因為Js的特性使得完全靜態(tài)分析是一個很難的事情,很多代碼往往會帶有副作用,比如一下代碼:

        class Handler {
            handleEvent() {
                console.log('You called me.')
            }
        }

        window.addEventListener('visibilitychange'new Handler())
        復制代碼

        在上面的代碼中不存在任何顯式的調(diào)用handleEvent,但當visibilitychange發(fā)生時Js會去調(diào)用handleEvent,這個類就屬于有副作用的一種,它是不能被抖掉的代碼(實際上webpack也不會對類有啥想法)。

        如果你確定某個文件是這種含有副作用的文件,可以在package.json中添加sideEffects: ['class.js']讓webpack強行打包進去。

        對于一些第三方庫來說為了兼容性考慮通常入口文件都是CommonJS的形式,這時想要成功抖掉不需要的部分通常有兩種方式。

        以出鏡率極高的lodash為例。

        lodash默認是CommonJS的形式,使用常規(guī)的方法import { cloneDeep } from 'lodash'導入后,webpack會把整個lodash打包進來,這對于只用到了一個函數(shù)的我們的來說顯然不可接受,此時可以改寫為:

        import cloneDeep from 'lodash/cloneDeep'
        復制代碼

        或者如果提供了ESModule的版本也可以直接使用:

        import { cloneDeep } from 'lodash-es
        復制代碼

        前者是精準導入不依賴re-exports,后者則是一個正經(jīng)的Tree-shaking。

        壓縮

        生產(chǎn)環(huán)境的代碼不是給人看的,所以不需要考慮可讀性(降低可讀性還能提高被破解的成本o(≧口≦)o),盡可能少的字符是最優(yōu)選項,webpack4+無需配置默認會壓縮代碼,如果你想親自試試,Js可選UglifyJS,CSS可選mini-css-extract-plugin。

        const MiniCssExtractPlugin = require('mini-css-extract-plugin');
        const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

        module.exports = {
          plugins: [
            new MiniCssExtractPlugin({
              filename'[name].css',
              chunkFilename'[id].css',
            }),
          ],
          module: {
            rules: [
              {
                test/\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'],
              },
            ],
          },
          optimization: {
            minimizer: [
              new CssMinimizerPlugin(),
            ],
          },
        };
        復制代碼

        使用動態(tài)import()代替靜態(tài)import做條件渲染的懶加載

        又是你~,懶加載。

        如果你是Vue選手,最先接觸到的import()可能是vue-router文檔中關于路由懶加載的部分,其實具體到組件內(nèi)部,也可以用同樣的方式將一些基于判斷條件的子組件/第三方庫通過import()的方式導入,這樣webpack在打包時會單獨將它列為一個塊,當符合判斷條件時才會嘗試去加載這個文件。

        <template>
            <div>
                <sub-component v-if="status" />
            </div>

        </tamplate>
        <script>
        export default {
            components: {
                "sub-component": () => import('./
        sub-component') // 感謝imluch 指正~ 

            },
            data() {
                return {
                    status: false
                }
            },
            mounted() {
                setTimeout(() => {
                    this.status = true
                }, 10000)
            }
        }
        </script>
        復制代碼

        SSR

        利用服務器端優(yōu)先渲染出某一部分重要的內(nèi)容,讓其他內(nèi)容懶加載,這樣到達瀏覽器端時一部分HTML已經(jīng)存在,頁面上就可以呈現(xiàn)出一定的內(nèi)容,這里注意服務器端渲染出來的HTML部分最好不要超過14kb,TCP慢開始的規(guī)則讓第一個TCP包的大小是14kb,這是與網(wǎng)站交互會接受到的第一個包。

        更多優(yōu)化手段

        上面是目前所知的優(yōu)化手段~,更多的優(yōu)化待發(fā)掘中,路過的大哥們、小姐姐們還請給個贊??,有問題的也可留言交流~??。

        zan.jpg

        最后

        歡迎關注【前端瓶子君】??ヽ(°▽°)ノ?
        回復「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
        回復「交流」,吹吹水、聊聊技術、吐吐槽!
        回復「閱讀」,每日刷刷高質(zhì)量好文!
        如果這篇文章對你有幫助,在看」是最大的支持
         》》面試官也在看的算法資料《《
        “在看和轉(zhuǎn)發(fā)”就是最大的支持


        瀏覽 56
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一A级黄色片 | 激情图片,激情视频 | 亚洲精品乱码久久久久久麻豆不卡 | 超碰色 | 白峰美羽亚洲中文字幕 | 久久色在线播放 | 91在线91拍拍在线91 | 色色香蕉网 | 在线观看亚洲AV无码 | 爱插综合网 |