從宏觀到細節爲你講解前端性能優化

前言

當我們去面試的時候,很大概率會被面試官問這麼一個問題:你有嘗試過對項目做性能優化嗎?或者你瞭解哪些性能優化的方法?聽到這個問題的你可能是這樣的:
在這裏插入圖片描述
似曾相識但又說不清楚,往往只能零散地說出那麼幾點,難以做到有條理的回答。那麼,本文就帶你簡單瞭解前端性能優化的幾個主要方面,旨在拋磚引玉。

一、資源的合併和壓縮

web前端應用的開發與部署過程:

image-20200330221214962

輸入url到頁面顯示出來的過程:

image-20200330221250064

請求過程中一些潛在的性能優化點:

  • dns是否可以通過緩存減少dns查詢時間?
  • 網絡請求的過程如何走最近的網絡環境?
  • 相同的靜態資源是否可以緩存?
  • 能否減少http請求的大小和次數?
  • 能否進行服務端渲染?

**總結:**深入理解http請求的過程是前端性能優化的核心。

優化核心

  • 減少http請求數量;
  • 減少請求資源的大小;

google首頁案例學習

  • html壓縮;
  • css壓縮;
  • js的壓縮和混亂;
  • 文件合併;
  • 開啓gzip

1.html壓縮

HTML代碼壓縮就是壓縮一些在文本文件中有意義,但是在HTML不顯示的字符,包括空格製表符換行符等,還有一些其他意義的字符,如**HTML註釋**也可以被壓縮;

一個簡單的計算:

google的流量,佔到整個互聯網的40%,預計2016年全球網絡流量將達到1.3ZB(1ZB = 10^9TB),那麼google2016年的流量就是1.3ZB * 40%,如果google1MB請求減少一個字節,**每年可以節省流量近500TB**流量。

如何進行html壓縮

  • 使用在線網站進行壓縮;
  • nodejs提供的html-minifier工具;
  • 後端模板引擎渲染壓縮;

2.css代碼壓縮

分爲兩部分:

  • 無效代碼的壓縮;
  • css語義合併;

如何進行css壓縮

  • 使用在線網站進行壓縮;
  • 使用html-minifierhtml中的css進行壓縮;
  • 使用clean-csscss進行壓縮;

3.js壓縮與混亂(醜化)

包括:

  • 無效字符的刪除(空格,回車等);
  • 剔除註釋;
  • 代碼語義的縮減和優化;
  • 代碼保護(如果代碼不經處理,客戶端可直接窺探代碼漏洞);

JS壓縮與混亂(醜化)

  • 使用在線網站進行壓縮:https://tool.oschina.net/jscompress/

image-20200330230443570

  • 使用html-minifierhtml中的js進行壓縮;
  • 使用uglify.js2js進行壓縮;

4.文件合併

文件合併的好處:

image-20200330224139300

左邊的表示使用http長鏈接keep-alive但不合並請求的情況,需要分三次去獲取a.jsb.jsc.js;右邊是使用長鏈接並且合併請求的情況,只需要發送一次獲取合併文件a-b-c.js的請求,就能將三個文件都請求回來。

不合並請求有下列缺點:

  • 文件與文件之間有插入的上行請求,會增加N-1個網絡延遲;
  • 受丟包問題的影響更嚴重:因爲每次請求都可能出現丟包的情況,減少請求能有效減少丟包情況;
  • keep-alive本身也存在問題:經過代理服務器時可能會被斷開;

文件合併存在的問題

  • 首屏渲染問題:當請求js文件的時候,如果頁面渲染只依賴a.js文件,由於文件合併,需要等待合併後的a-b-c.js文件請求回來才能繼續渲染,這樣就會導致頁面渲染速度變慢。這種情況大多出現在現代化的前端框架,如Vue等的使用過程中;
  • 緩存失效問題:合併後的文件a-b-c.js中只要其中一個文件(比如a.js)發生變化,那麼整個合併文件都將失效,而不採用文件合併就不會出現這種情況;

使用建議

  • 公共庫合併:將不經常發生變化的公共組件庫文件進行合併;
  • 將不同頁面的js文件單獨合併:比如在單頁面應用SPA中,當路由跳轉到具體的頁面時才請求該頁面需要的js文件;

如何進行文件合併

  • 使用在線網站進行文件合併;
  • 使用nodejs實現文件合併;
  • 使用webpack等前端構件化工具也可以很好地實現;

二、圖片相關的優化

有損壓縮過程:

image-20200330232432166

一張JPG圖片的解析分別要進行:

  • 顏色空間的轉換:從RGB的顏色空間轉到其他的顏色空間 ;
  • 進行重採樣:區分高頻和低頻的顏色變換;
  • 進行DCT過程:對高頻的顏色採樣結果進行壓縮,這樣壓縮的收益會比較大;
  • 再對數據進行量化;
  • 最後進行編碼(encoding);

最終得到JPEG-Compressed Image Data,即真正顯示出來的JPG圖片。雖然這是一種有損壓縮,但是很多情況下,這些損失的數據並不影響顯示;

png8/png24/png32之間的區別

  • png8256+ 支持透明;
  • png242^24+ 不支持透明;
  • png322^32+ 支持透明;

不同格式圖片常用的業務場景

  • jpg有損壓縮,壓縮率高,支持透明;應用:大部分不需要透明圖片的業務場景;
  • png支持透明,瀏覽器兼容好;應用:大部分需要透明圖片的業務場景;
  • webp2010年由谷歌推出)壓縮程度更好,在ios webview中有兼容性問題;應用:安卓全部;
  • svg矢量圖,代碼內嵌,相對較小,用於圖片樣式相對簡單的場景;應用:比如logoiconfont

1.圖片壓縮

針對真實圖片情況,捨棄一些相對無關緊要的色彩信息,對圖片進行壓縮;比如在線壓縮網站:https://tinypng.com/

image-20200331102612277

2.css雪碧圖

將網站上用到的一些圖片整合到一張單獨的圖片中,從而減少網站HTTP請求數量。原理爲:設定整張雪碧圖可示區域,將想要顯示的圖標定位到該處(左上角);**缺點:**整合圖片比較大時,一次加載比較慢。

如天貓的雪碧圖:

image-20200331100812635

很多情況下,並不是所有的小圖標都放在一張雪碧圖中,而是會適當進行拆分。現在使用雪碧圖的場景比較少了。

自動生成雪碧圖樣式的網站:http://www.spritecow.com/

image-20200331111632363

選中雪碧圖中對應的圖標,就會生成對應的樣式。

3.網頁內聯圖片(Image inline

將圖片的內容內嵌到html當中,減少網站的HTTP請求數量,常用於處理小圖標和背景圖片。網頁內聯圖片寫法爲:

 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..." alt="">

瀏覽器上的表現形式爲:

image-20200331105927649

這裏提供一個將:imageDataUrI的網址:http://tool.c7sky.com/datauri/

image-20200331105719573

缺點:

  • 瀏覽器不會緩存內聯圖片資源;
  • 兼容性較差,只支持ie8以上瀏覽器;
  • 超過1000kb的圖片,base64編碼會使圖片大小增大,導致網頁整體下載速度減慢;

所以要根據場景使用,不過內聯圖片減少HTTP請求的優點還是很顯著的。比如,在開發中小於4KB8KB的圖片都會通過構建工具自動inlineHTML中,這種情況下Image inline帶來的圖片大小增長其實是比增加HTTP請求次數更優的。

4.矢量圖SVGiconfont

使用iconfont解決icon問題

應儘量使用該方式,比如可以採用阿里巴巴矢量圖庫:

image-20200331102136261

可以選擇格式進行下載:

image-20200331102259285

可以看到它們的大小有着明顯的差異:

image-20200331102353635

使用SVG進行矢量圖的控制

SVG意爲可縮放矢量圖形(Scalable Vector Graphics)。SVG 使用 XML 格式定義圖像。下爲示例:

image-20200331112542540

在線轉換網站:http://www.bejson.com/convert/image_to_svg/

image-20200331112802316

5.webp

webp的優勢體現在它具有更優的圖像壓縮算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的圖像質量;同時具備了無損和有損的壓縮模式、Alpha透明以及動畫的特性。在JPEGPNG上的轉化效果都非常優秀、穩定和統一。安卓上不存在兼容性問題,推薦安卓下使用。

以下爲淘寶網首頁請求的圖片:
image-20200331101234068

可以看到,圖片中大量地添加了webp格式的選擇。.jpg_.webp表示當瀏覽器支持webp時採用webp格式,否則採用jpg格式。

下面爲B站首頁的圖片,可以看到基本都採用了webp格式:

image-20200401110811561

同一張圖片jpg格式和webp格式壓縮率有着明顯的差異:

image-20200331101633934

可以通過在線網站將圖片轉換爲webp:https://zhitu.isux.us/

image-20200331103230221

像圖片這樣的靜態文件可以存放在CDN服務器上,讓CDN服務器批量地將圖片轉換成Webp格式;

三、瀏覽器渲染引擎與阻塞

1.渲染的主要模塊

版本一:

image-20200331131925229

版本二:

image-20200331113305758

一個渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,佈局layout模塊,繪圖模塊:

  • HTML解析器:解釋HTML文檔的解析器,主要作用是將HTML文本解釋成DOM樹;
  • CSS解析器:它的作用是爲DOM中的各個元素對象計算出樣式信息,爲佈局提供基礎設施;
  • Javascript引擎:使用Javascript代碼可以修改網頁的內容,也能修改css的信息,javascript引擎能夠解釋javascript代碼,並通過DOM接口和CSS樹接口來修改網頁內容和樣式信息,從而改變渲染的結果;
  • 佈局(layout):在DOM創建之後,Webkit需要將其中的元素對象同樣式信息結合起來,計算他們的大小位置等佈局信息,形成一個能表達這所有信息的內部表示模型;
  • 繪圖模塊(paint):使用圖形庫將佈局計算後的各個網頁的節點繪製成圖像結果;

2.渲染過程

瀏覽器渲染頁面的整個過程:瀏覽器會從上到下解析文檔。

  1. 瀏覽器解析時遇見 HTML 標記,就會調用HTML解析器解析爲對應的 token (一個token就是一個標籤文本的序列化)並構建 DOM 樹(就是一塊內存,保存着tokens,建立它們之間的關係)。在生成DOM的最開始階段(應該是Bytescharacters後),並行發起css、圖片、js的請求,無論他們是否在HEAD標籤中。

    注意:發起js文件的下載請求(request)並不需要DOM處理到那個script節點;

  2. 遇見 style/link 標記 調用解析器 處理 CSS 標記並構建 CSS樣式樹;

  3. 遇見 script 標記 調用 javascript解析器處理script標記,綁定事件、修改DOM樹/CSS樹等;

  4. DOM樹 與 CSS樹 合併成一棵渲染樹(Render Tree)。

  5. 佈局(Layout):根據渲染樹中各節點的樣式和依賴關係,計算出每個節點在屏幕中的位置;

  6. 繪圖(Painting):按照計算出來的結果:要顯示的節點、節點的CSS與位置信息,通過顯卡,把內容畫到屏幕上;

經過第一次Painting之後DOMCSSOMRender Tree都可能會被多次更新,比如JS修改了DOM或者CSS屬性時,LayoutPainting就會被重複執行。除了DOMCSSOM更新的原因外,圖片下載完成後也需要調用LayoutPainting來更新網頁。

補充:

  • HTML中可能會引入很多的css、js這樣的外部資源,這些外部資源在瀏覽器端是併發加載的。但是瀏覽器會對同一域名進行併發數量(度)的限制,即單個域名的併發度是有限的;
  • 所以,經常將大部分的資源託管到CDN服務器上,並且設置3~4CDN域名。防止只有一個CDN域名的情況下,達到了瀏覽器外部資源併發請求數目的上限,導致很多資源無法做到併發請求。所以,應設置多個CDN域名;

3.css阻塞

只有通過link引入的外部css纔會產生阻塞:

  • style標籤中的樣式:

    • html解析器進行解析;
    • 不阻塞瀏覽器渲染(可能會產生“閃屏現象”);
    • 不阻塞DOM解析;
  • link引入的外部css樣式(推薦使用的方式):

    • CSS解析器進行解析;
    • 阻塞瀏覽器渲染:由於css已經加載完畢,所以整個渲染過程是帶樣式的,所以這種阻塞可以避免“閃屏現象”;
    • 阻塞其後面的js語句的執行:這個不難理解,js文件中經常會出現DOM操作,操作過程中有可能涉及到css樣式的修改。實際上,這些修改往往是依賴於之前引入的css設定的樣式的,所以css會阻塞js的執行;
    • 不阻塞DOM的解析;
  • 優化核心理念:儘可能快的提高外部css加載速度:

    • 使用CDN節點進行外部資源加速;
    • css進行壓縮(利用打包工具,比如webpackgulp等);
    • 減少http請求數,將多個css文件合併;
    • 優化樣式表的代碼;

4.js阻塞

  • 阻塞DOM解析:

    原因:瀏覽器不知道後續腳本的內容,如果先去解析了下面的DOM,而隨後的js刪除了後面所有的DOM,那麼瀏覽器就做了無用功,瀏覽器無法預估腳本里面具體做了什麼操作,例如像document.write這種操作,索性全部停住,等腳本執行完了,瀏覽器再繼續向下解析DOM;可以通過給script標籤添加deferasync屬性,異步引入js文件,以此來解決這一問題。

  • 阻塞頁面渲染:

    原因:js中也可以給DOM設置樣式,瀏覽器同樣等該腳本執行完畢,再繼續幹活,避免做無用功;

  • 阻塞後續js的執行:

    原因:js是按順序執行的,這樣可以維護依賴關係,例如:必須先引入jQuery再引入bootstrap

  • 不阻塞資源的加載:

    這並不與上面矛盾,因爲不可能由於加載一個js文件就把其他資源的加載都阻塞了。針對這種常見的情況,瀏覽器會通過預加載的方式加載後續的資源;

5.總結

  • css的解析和js的執行是互斥的(互相排斥),css解析的時候js停止執行,js執行的時候css停止解析;

  • 無論css阻塞,還是js阻塞,都不會阻塞瀏覽器加載外部資源(圖片、視頻、樣式、腳本等);

    因爲覽器始終處於一種:“先把請求發出去”的工作模式,只要是涉及到網絡請求的內容,無論是:圖片、樣式、腳本,都會先發送請求去獲取資源,至於資源到本地之後什麼時候用,由瀏覽器自己協調。顯然這種做法效率很高;

  • WebKitFirefox 都進行了【預解析】這項優化。在執行js腳本時,瀏覽器的其他線程會解析文檔的其餘部分,找出並加載需要通過網絡加載的其他資源。通過這種方式,資源可以在並行連接上加載,從而提高總體速度。請注意,預解析器不會修改 DOM

四、懶加載和預加載

1.懶加載

圖片進入可視區域之後再請求圖片資源的方式稱爲圖片懶加載。適用於圖片很多,頁面很長的業務場景,比如電商;

懶加載的作用:

  • 減少無效資源的加載:

    比如一個網站有十頁圖片,用戶只查看了第一頁的圖片,這就沒必要將十頁圖片全都加載出來;

  • 併發加載的資源過多會阻塞js的加載,影響網站正常的使用:

    由於瀏覽器對某一個host name是有併發度上限的,如果圖片資源所在的CDN和靜態資源所在的CDN是同一個的話,過多圖片的併發加載就會阻塞後續js文件的併發加載。

懶加載實現的原理:

監聽onscroll事件,判斷可視區域位置:

圖片的加載是依賴於src路徑的,首先可以爲所有懶加載的靜態資源添加自定義屬性字段,用於存儲真實的url。比如是圖片的話,可以定義data-src屬性存儲真實的圖片地址,src指向loading的圖片或佔位符。然後當資源進入視口的時候,纔將src屬性值替換成data-src中存放的真實url

<img src="" class="image-item" alt="" lazyload = "true" data-src="TB27YQvbm_I8KJjy0FoXXaFnVXa_!!400677031.jpg_180x180xzq90.jpg_.webp">

懶加載實例

可以使用元素的getBoundingRect().top來判斷當前位置是否在視口內,也可以使用元素距離文檔頂部的距離offsetTopscrollTop是否小於視口高度來判斷:

image-20200331153831469

舉例

比如手機淘寶首頁:

image-20200331145210486

當快要滾動到需要展示的圖片時才進行圖片的請求,可以看到圖片上有一個lazyload的屬性:

image-20200331145756313

2.預加載

預加載與懶加載正好是相反的過程:懶加載實際上是延遲加載,將我們所需的靜態資源加載時間延後;而預加載是將圖片等靜態資源在使用之前的提前請求,這樣資源在使用到時能從緩存中直接加載,從而提升用戶體驗;

預加載的作用:

  • 提前請求資源,提升加載速度:使用時只需要讀取瀏覽器緩存中提前請求到的資源即可;

  • 維護頁面的依賴關係:比如WebGL頁面,會依賴一些3D模型,這些都是頁面渲染所必須的資源。如果資源都沒有加載完畢就進行頁面的渲染,就會造成非常不好的體驗。

    所以時常使用預加載的方式維護頁面渲染的依賴關係,比如將WebGL頁面依賴的3D模型加載完之後才進行頁面渲染。這樣渲染的過程就不會有任何阻礙,具有較好的用戶體驗;

預加載的實例

例如九宮格抽獎業務,每個獎品都有一個選中態和非選中態,實際上這是由兩張圖片組合而成的。由於每個獎品的選中過程都是一瞬間,這就對圖片的選中態和非選中態切換效率要求很高,如果選中態的圖片沒有預加載的話顯然是來不及的。

image-20200331150725347

所以,實際上對於九宮格中所有圖片選中態的樣式和對應的圖片都需要進行預加載,從而讓我們在抽獎的過程中,能夠瞬間從緩存中讀取到選中態的圖片,從而不影響抽獎效果的展示。

除此之外還有網站登錄或活動時需要用到的動畫,這是在動畫需要的每幀圖片都完全預加載完之後纔會進行顯示的。

五、重繪與迴流

1.CSS圖層

瀏覽器在渲染一個頁面時,會將頁面分爲很多個圖層,圖層有大有小,每個圖層上有一個或多個節點。在渲染 DOM的時候,瀏覽器所做的工作實際上是:

​ 1、獲取DOM後分割爲多個圖層;

​ 2、對每個圖層的節點計算樣式結果(Recalculate style–樣式重計算);

​ 3、爲每個節點生成圖形和位置(Layout–迴流和重佈局);

​ 4、將每個節點繪製填充到圖層位圖中(Paint SetupPaint–重繪);

​ 5、圖層作爲紋理上傳至GUI

​ 6、複合多個圖層到頁面上生成最終屏幕圖像(Composive Layers–圖層重組);

2.創建圖層的條件

  • 擁有3D或透視變換的css屬性(prespective transform );
  • 使用加速視頻解碼的<video>節點;
  • 擁有3D(WebGL)上下文或加速的2D上下文的<canvas>節點;
  • CSS3動畫的插件(如Flash);
  • 擁有加速css過濾器的元素;
    • transform:如translateZ(0)
    • opacity
    • filter
    • will-change:哪一個屬性即將發生變化,進而進行優化。

3.重繪(Repaint)

重繪是一個元素外觀的改變所觸發的瀏覽器行爲,比如background-coloroutline等屬性。這些屬性不影響佈局,隻影響元素的外觀風格,會造成DOM元素的重新渲染,這個過程稱爲重繪。

需要注意的是:重繪是以圖層爲單位,如果圖層中某個元素需要重繪,那麼整個圖層都需要重繪。比如一個圖層包含很多節點,其中有個gif圖,gif圖的每一幀,都會重回整個圖層的其他節點,然後生成最終的圖層位圖。

因此,可以通過特殊的方式來強制gif圖單獨爲一個圖層(translateZ(0)或者translate3d(0,0,0)CSS3的動畫也是一樣(好在絕大部分情況瀏覽器自己會爲CSS3動畫的節點創建圖層);

所以:頻繁重繪迴流DOM元素作爲一個獨立圖層,那麼這個DOM元素的重繪和迴流只會該圖層;原則上是要儘量避免新建圖層的,因爲這會導致圖層重組(Composive Layers)時候的計算量增大。所以,只有當某些DOM元素頻繁重繪迴流時,才新建一個獨立圖層放置它們;

只會觸發重繪的屬性

//部分屬性
color
border-style
border-radius
visibility
text-decoration
background
background-image
background-position
background-repeat
background-size
outline-color
outline
outline-style
outline-width
box-shadow

4.迴流(Reflow)

render tree中的一部分(或全部)因爲元素的規模尺寸佈局隱藏等改變而需要重新構建。這就稱爲迴流(reflow);

  • 當頁面佈局和幾何屬性改變時就需要回流;

  • 迴流必將引起重繪,而重繪不一定會引起迴流;

觸發頁面重佈局(迴流)的屬性

盒子模型相關屬性 定位及浮動屬性 文字結構屬性
width top text-align
height bottom overflow-y
padding left font-weight
margin right overflow
display position font-family
border-width float line-height
border clear vertical-align
min-height * white-space
* * font-size

頻繁觸發重繪迴流,會導致UI頻繁渲染。在渲染的過程中由於阻塞了js線程的執行,最終導致js執行變慢。

5.觸發迴流的常見操作

  • 增加、刪除、修改 DOM 結點;

  • 移動 DOM 的位置;

  • 修改 CSS 樣式;

  • Resize 窗口;移動端沒有這個問題,因爲移動端的縮放沒有影響佈局視口(vw/vh);

  • 修改網頁的默認字體;

  • 獲取某些DOM元素的屬性(widthheight等);

display:none 會觸發 Reflow,而visibility:hidden 只會觸發 Repaint,因爲沒有發生位置變化;

6.示例

案例一:淘寶輪播圖

可以使用Chrome瀏覽器調試工具的Performance來觀察淘寶首頁一個輪播圖引起的重繪迴流過程:

image-20200331161654830

Update Layer Tree迴流和重佈局:

image-20200331163602611

Paint重繪:

image-20200331163618288

Composite Layers圖層重組:

image-20200331163626477

案例二:播放器

通過Chrome調試工具的Layers選項查看圖層,及新增圖層的原因:

image-20200331162710936

視頻播放的過程中,video標籤的DOM元素會一直重繪,所以把它限制在一個圖層上是非常好的,這樣只會涉及到這個圖層的重繪,而不會影響其他圖層的元素。

圖層不能濫用,否則會在圖層重組的過程中嚴重消耗性能!

比如可以將淘寶首頁的所有的DOM元素都變爲一個圖層:在html標籤中的全局樣式(*)中添加transform:translateZ(0)來觸發新建圖層:

還可以通過添加:will-change: transform屬性新建圖層;

image-20200331165429542

再次查看此時的圖層情況,可以看到此時首頁的圖層非常之多,十分地卡:

image-20200331165818801

7.實戰優化點

如果我們需要使得動畫或其他節點渲染的性能提高,需要做的就是減少瀏覽器在運行時所需要做的下列工作:

  • 計算需要被加載到節點上的樣式結果(Recalculate style–樣式重計算);
  • 爲每個節點生成圖形和位置(Layout–迴流和重佈局);
  • 將每個節點填充到圖層中(Paint SetupPaint–重繪);
  • 組合圖層到頁面上(Composite Layers–圖層重組);

1、使用translate替代top等屬性來改變位置;

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box{
  	 /*方法1*/
      position: relative;
      top: 0;
     
     /*方法2*/
     /* transform: translateY(0); */
        
      width: 200px;
      height: 200px;
      background-color: pink;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <script>
    setTimeout(() => {
      document.getElementById("box").style.top = '100px'
      //document.getElementById("box").style.transform = 'translateY(100px)'
    }, 2000);
  </script>
</body>
</html>

使用top屬性改變正方形位置時,存在重繪和迴流Layout

image-20200331173140790

而使用translate屬性改變正方形位置時,並不會引起重繪和迴流:
image-20200331173755614

比如有的網站會有一些左右飄動的浮窗,由於這些浮窗是採用定時器來實現的,如果每經過100ms就改變浮窗的位置。這種時候使用transform來替代top/left的話1s內就減少了十次迴流,十分有利於網頁速度的提升。

2、使用opacity替代visibility

  • 使用visibility不觸發重排,但是依然重繪;

  • 直接使用opacity既觸發重繪,又觸發重排(GPU底層設計如此!);

  • opacity配合圖層使用,既不觸發重繪也不觸發重排;

    原因:透明度的改變時,GPU在繪畫時只是簡單的降低之前已經畫好的紋理的alpha值來達到效果,並不需要整體的重繪。不過這個前提是這個被修改opacity本身必須是一個單獨的圖層。

3、將多次改變DOM元素樣式屬性的操作合併成一次操作:

  • 預先定義好class,然後通過修改DOMclassName來添加樣式;

4、把DOM離線後再修改:

  • 由於display屬性爲none的元素不在渲染樹中,對隱藏的元素操作不會引發其他元素的重排。如果要對一個元素進行復雜的操作時,可以先隱藏它,操作完成後再顯示。這樣只在隱藏和顯示時觸發2次迴流;

5、不要把獲取某些DOM節點的屬性值放在一個循環裏當成循環的變量

當向瀏覽器請求某些 style信息的時候,瀏覽器就會清空(flush)隊列,比如:

  • ffsetTopoffsetLeftoffsetWidthoffsetHeight

  • scrollTop/Left/Width/Height

  • clientTop/Left/Width/Height

  • widthheight

瀏覽器爲了獲取最精確的值,需要刷新內部隊列。因爲隊列中可能存在影響到這些值的操作,即使沒有,瀏覽器也會強行刷新渲染隊列。這樣就無法利用渲染隊列的緩存來避免迴流過於頻繁了,所以在使用到DOM元素這些相關的屬性時,可以將獲取到的屬性值存在一個變量中,而不是每次都去重新獲取。

6、不要使用table佈局:

  • 因爲很小的一個改動都會造成整個table的重新佈局;所以儘量使用div佈局;

7、啓用GPU硬件加速:

原理爲:瀏覽器會檢測一些特定的css屬性,當DOM元素擁有這些css屬性的時候,瀏覽器就會對該DOM元素啓動GPU硬件加速;比如:transform: translateZ(0)transform: translate3d(0, 0, 0)這兩個屬性都可以啓動硬件加速;硬件加速同樣不能濫用,否則會導致圖層過多,導致合併圖層時消耗大量性能。

8、動畫實現速度的選擇:

  • 因爲動畫的每次變化都會引起重繪和迴流,所以要根據業務場景適當地在動畫幀數(順暢程度)和迴流次數中進行平衡;

9、爲動畫元素新建圖層,提高動畫元素的z-index

10、利用文檔碎片(documentFragment)------vue使用了該種方式提升性能

如果我們要在一個ul中添加10000li,如果不使用文檔碎片,那麼我們就需要使用append進行10000次的追加,這會導致頁面不停地迴流,非常地消耗資源:

var oUl = document.createElement("ul"); 
for(var i=0;i<10000;i++)
{ 
    var oLi = document.createElement("li"); 
    oUl.appendChild(oLi); 
} 
document.body.appendChild(oUl); 

我們可以引入createDocumentFragment()方法,它的作用是創建一個文檔碎片。先將要插入10000li添加到文檔碎片裏,然後再一次性添加到document中。即文檔碎片相當於一個臨時倉庫,這樣能夠大量減少DOM操作:

//先創建文檔碎片
var oFragment = document.createDocumentFragment(); 

//再創建ul標籤
var oUl = document.createElement("ul"); 
for(var i=0;i<10000;i++)
{ 
  //創建li標籤
  var oLi = document.createElement("li"); 
  //先附加在文檔碎片中
  oFragment.appendChild(oLi);  
}
//將文檔碎片添加到ul標籤中 
oUl.appendChild(oFragment);
//將ul標籤添加到body標籤中
document.body.appendChild(oUl); 

**10、使用requestAnimationFrame製作動畫:**詳細內容如下。

8.請求動畫幀(requestAnimationFrame

**window.requestAnimationFrame() :**該方法會告訴瀏覽器在重繪之前調用指定的函數:

  • **參數:**該方法以一個回調函數作爲參數,這個回調函數會在瀏覽器重繪之前被調用;

    回調函數會被自動傳入一個參數:DOMHighResTimeStamp,標識requestAnimationFrame()開始觸發回調函數的當前時間;

  • 返回值: 一個非零的整數,也稱爲請求ID,是回調列表中唯一的標識,沒有其他意義;

**window.cancelAnimationFrame(requestID):**該方法取消一個先前通過調用window.requestAnimationFrame()方法添加到計劃中的動畫幀請求。requestID是先前調用window.requestAnimationFrame()方法時返回的ID

用途

  • 當無法使用CSS3製作動畫的情況下,使用這種方法替代定時器製作動畫;
  • 由於重繪就調用的機制,製作的動畫頻率與瀏覽器的刷新頻率一致,不會出現閃動,保證了動畫的流暢;

示例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box{
      height: 200px;
      width: 200px;
      background-color: pink;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <script>
    let i = 0
    //獲取請求ID
    let id = requestAnimationFrame(move)

    function move(){
      i++
      document.getElementById('box').style.transform = `translateX(${i}px)`
      //遞歸調用requestAnimationFrame,更新請求ID,實現動畫效果
      id = requestAnimationFrame(move)
    }

    setTimeout(() => {
      //2s後停止動畫
      cancelAnimationFrame(id)
    }, 2000);
  </script>
</body>
</html>

六、函數防抖與節流

1.函數防抖

  • **概念:**不斷觸發一個函數,在規定時間內只讓最後一次生效,前面都不生效;
  • **實現:**定時器;
  • **應用:**搜索時等用戶完整輸入內容後再發送查詢請求;

代碼實現

function debounce(fn,delay){
           var timer = null
        //  清除上一次延時器
          return function(){
               clearTimeout(timer)
              //  重新設置一個新的延時器
              timer = setTimeout(() => {
                  fn.call(this)
              }, delay);
          }
       }

使用函數防抖可以減少事件觸發的次數和頻率,在某些情況下可以起到優化的作用。比如:搜索框,對於核心業務非搜索的網站,一般都是等待用戶完整輸入內容後才發送查詢請求,一次來減少服務器的壓力。像百度這樣的核心業務爲搜索的網站,服務器性能足夠強大,所以不進行函數防抖處理;

2.函數節流

  • **概念:**不斷觸發一個函數後,執行第一次,只有大於設定的執行週期後纔會執行第二次,以此控制函數執行頻率;

  • **實現:**定時器,標識;

  • **應用:**在遊戲中,可以設定人物攻擊動作的最快頻率,無論手速多快也無法超越這一頻率;

代碼實現

/* 
	節流函數:fn:要被節流的函數,delay:規定的時間
 */
function throttle(fn, delay){
    // 記錄上一次函數出發的時間
    var lastTime = 0
    return function(){
        // 記錄當前函數觸發的時間
        var nowTime = new Date().getTime()
        // 噹噹前時間減去上一次執行時間大於這個指定間隔時間才讓他觸發這個函數
        if(nowTime - lastTime > delay){
            // 綁定this指向
            fn.call(this)
            //同步時間
            lastTime = nowTime
        }
    }
}

七、瀏覽器存儲

1.Cookie

  • Cookie翻譯過來是小甜餅的意思,是網景公司的前僱員 Lou Montulli19933月發明的;
  • Cookie是純文本格式,不包含任何可執行的代碼信息,伴隨着用戶請求在 Web 服務器和瀏覽器之間傳遞;
  • Cookie本質上屬於http的範疇,因爲http協議本身是無狀態的,服務端是沒有辦法區分請求來自於哪個客戶端,即便是來自於同一個客戶端的多次請求,服務端也無法進行區分。所以引入了Cookie去維持客戶端的狀態(比如每個賬號的購物車狀態都不一樣)。

Cookie的生成方式

  • 客戶端生成:

    JavaScript 中通過 document.cookie 屬性,你可以創建、維護和刪除 Cookie;設置 document.cookie 屬性的值並不會刪除存儲在頁面中的所有 Cookie,它只簡單的創建或修改字符串中指定的 Cookie

  • 服務端生成:

    Web 服務器通過在HTTP響應頭中添加 Set-Cookie字段來創建一個 Cookie,可以在該字段中添加HttpOnly屬性禁止JavaScript腳本訪問Cookie,以此來避免跨域腳本 (XSS) 攻擊。

Cookie的缺陷

  • **安全性:**由於CookieHTTP中是明文傳遞的,其中包含的數據都可以被他人訪問,出現篡改、盜用等問題;
  • 大小限制:Cookie的大小限制在4KB左右,若要做大量存儲顯然不是理想的選擇;
  • **增加流量:**因爲Cookie是綁定域名對應的服務器的,所以對同一個域名的每次請求都會在Request Header中帶上Cookie
    • 一方面:增加對服務器的請求時間;
    • 另一方面:導致大部分不需要用到Cookie信息的場合下流量的浪費;這樣瀏覽器對同一域名的每一次請求都會多出4KB流量,對於大型網站來說這是很大的損耗。

因此要慎用Cookie,不要在Cookie中存儲重要和敏感的數據。

Cookie性能優化的方法

將存放靜態資源的CDN服務器域名與主站的域名獨立開來。這樣每次請求靜態文件的時候就不需要攜帶Cookie,從而可以節省很多流量。

舉例

比如在百度進行登錄的時候,請求頭裏面就會有Set-Cookie字段,其中的BDUSS就是標識用戶登錄狀態的字符串:

image-20200331213122597

Set-Cookie中的httponly屬性表示的是禁止js腳本訪問cookie,這樣能夠一定程度防範XSS攻擊;

Chrome調試工具的Application選項中查看Cookies信息,可以發現該Cookie已經被網站“種”到Domain:.baidu.com這個域名下了,並且該Cookie也設置了HttpOnly屬性:

image-20200331213850374

此後瀏覽器的每次請求都會在請求頭Request Headers中攜帶這一Cookie信息。刷新頁面後可以看到,請求頭中攜帶了Cookie信息BDUSS

image-20200331214126035

這樣服務器就知道這是已經登錄的用戶了。

但是不是所有的請求都需要攜帶Cookie信息,比如優酷:

image-20200331214741967

可以看到請求index.css文件時也攜帶了Cookie,但是這是不必要的,這就會導致流量的浪費。

解決方法就是上面所說的:將**CDN域名主域名**獨立出來;

百度就是這樣解決的:

image-20200331215055075

可以看到請求這個靜態資源的url並不是.baidu.com,而是靜態資源服務器CDN;並且該請求的請求頭中不會攜帶Cookie信息:

image-20200331215256387

設置和獲取Cookie

設置Cookie的方式很簡單,keyvalue值通過等號連接:

document.cookie = "userName=zhangsan"

image-20200331232648641

打開Application選項查看當前Cookie,可以看到Cookie已被改變:

image-20200331232745709

獲取Cookie

document.cookie

image-20200331233517456

備註:

  • 靜態資源是不會攜帶Cookie的;
  • Cookie一般都是後臺種的,很少讓前端來直接寫;
  • Cookie分:持久級別、session級別;
  • Cookie一般用於存放session ID與服務器端進行通信;

2.Web Storage

  • Web Storage分爲SessionStorageLocalStorage專門用於客戶端瀏覽器的本地存儲,同時空間比Cookie大很多,一般支持5-10M

  • 瀏覽器端通過 Window.sessionStorageWindow.localStorage 屬性來實現本地存儲機制;

LocalStorage

LocalStorageHTML5設計出來專門用於存儲瀏覽器信息的:

  • 大小爲5~10M左右;
  • 僅在客戶端中使用,不和服務端進行通信;
  • 接口封裝較好,提供了js進行讀寫等操作的API
  • 採用瀏覽器本地緩存方案,可直接使用瀏覽器本地緩存,提升網頁渲染的速度;

舉例

比如通過Chrome調試工具的Application選項可以查看淘寶中LocalStorage存儲的數據:

image-20200331220258429

這些數據只要不手動清除,即使關閉頁面也都會存在。當需要使用圖片、js/css文件等資源時就不用重新向服務器發出請求,而是可以直接使用LocalStorage中的緩存,這就是LocalStorage緩存的優勢;

Cookie就不一樣了,裏面存儲的數據都是要帶到服務器端的,例如用戶登錄狀態,統計信息等數據:
image-20200331220548331

設置和獲取LocalStorage

LocalStorage提供了相對簡單的API,採用的也是keyvalue的形式。

設置時通過:

localStorage.setItem("key", "value")

image-20200331233022902

查看LocalStorage,同樣設置成功了:

image-20200331233059670

獲取時通過:

localStorage.getItem("key")

image-20200331233226621

其他方法

//該方法接受一個鍵名作爲參數,並把該鍵名從存儲中刪除。
localStorage.removeItem('key');
	
//調用該方法會清空存儲中的所有鍵名
localStorage.clear();

SessionStorage

SessionStorage用於存儲瀏覽器的會話信息,標籤頁關閉之後它存儲的數據就會被清空,而LocalStorage的數據不會被清空,這是二者的區別:

  • 大小爲5~10M左右;
  • 僅在客戶端使用,不和服務端進行通信;
  • 接口封裝較好;
  • 可對錶單信息進行維護;比如添加表單過程中進行了刷新,可以將刷新前填寫的信息寫入SessionStorage中,這樣即使刷新後數據也不會丟失;還有一種場景:分頁的表單在進行前進或後退時,如果將信息保存在SessionStorage中就不會丟失;

設置和獲取SessionStorage

設置SessionStorage的方法與設置LocalStorage的方法類似:

//設置
sessionStorage.setItem("key", "value")

//獲取
sessionStorage.getItem("key")

image-20200331233922185

通過Application選項查看SessionStorage,可見已成功修改:

image-20200331234022971

其他方法

//該方法接受一個鍵名作爲參數,並把該鍵名從存儲中刪除。
sessionStorage.removeItem('key');
	
//調用該方法會清空存儲中的所有鍵名
sessionStorage.clear();

3.IndexedDB

IndexedDB是瀏覽器提供的一種API,用於存儲客戶端中大量的結構化數據。該API使用索引來實現對數據的高性能搜索。雖然WebStorage對於存儲較少量的數據時很有用(採用key/value的方式),但對於存儲更大量的結構化數據來說,還是IndexedDB表現更加優異。

IndexedDB的應用

  • 爲應用創建離線版本;

可以在瀏覽器中打印indexedDB對象:

image-20200331234812475

4.PWA

PWAProgressive Web Apps)是一種Web App新模型(標準),並不是具體指某一種前沿的技術或者某一個單一的知識點。從英文縮寫就能看出,這是一個漸進式的Web App,是通過一系列新的Web特性,配合優秀的UI交互設計,逐步增強用戶的體驗;

PWA的要求

  • **可靠:**在沒有網絡的環境中也能提供基本的頁面訪問,而不會出現"未連接到互聯網"的情況;
  • **快速:**針對網頁渲染及網絡數據訪問有較好優化;
  • **融入(Engaging):**應用可以被增加到手機桌面,並且和普通應用一樣有全屏、推送等特性;

5.Service Worker

Service Worker是一個腳本,可以使瀏覽器獨立於當前網頁,在後臺運行。爲實現一些不依賴頁面或者用戶交互的特性打開了一扇大門。在未來這些特性將包括推送信息,背景後臺同步,geofencing(地理圍欄定位)等它將推出的第一個首要特性,就是攔截和處理網絡請求的能力,包括以編程方式來管理被緩存的響應。

Service Worker可以幫助瀏覽器執行大規模的運算而不阻礙主線程的執行。

Service Worker的應用

  • 使用攔截和處理網絡請求的能力,實現一個離線應用;
  • 使用Service Worker在後臺運行的同時能和頁面通信的能力,去實現大規模後臺數據的處理;

Service Worker應用過程

image-20200331235052759

示例

通過Chrome調試工具的Application選項可以查看淘寶的Service Workers信息:

image-20200331230014013

當我們刷新淘寶網頁的時候,查看Network選項,可以從請求文件的size欄發現大量的文件都是從Service Worker緩存中請求回來的:

image-20200331230221993

這樣的話就可以利用Service Worker的緩存進行網站的性能優化。

以下列淘寶請求同一js文件爲例,從Service Worker中加載使用了7ms

image-20200331230805709

使用Ctrl + F5強制刷新後,向服務器請求同一文件花了100ms

image-20200331230936902

這就是使用Service Worker性能上帶來的優勢。由於是從本地緩存中讀取的資源,所以資源讀取的速度和整體的性能都會有一個明顯的提升。

八、HTTP通用緩存策略

1.緩存的簡介

  • 緩存定義:

    瀏覽器在本地磁盤上將用戶之前請求的數據存儲起來,當訪問者再次需要改數據的時候無需再次發送請求,直接從瀏覽器本地獲取數據

  • 緩存的好處:

    • 減少請求的個數;
    • 節省帶寬,避免浪費不必要的網絡資源;
    • 減輕服務器壓力;
    • 提高瀏覽器網頁的加載速度,提高用戶體驗;

2.緩存相關的header字段

可以通過Chrome瀏覽器調試工具中的Network選項查看瀏覽器請求資源的情況:

image-20200401134006392

注意不要勾選圖中方框內的選項,否則有些請求會被過濾;

Cache-Control字段

服務器可通過httpheader中的Cache-Control字段控制客戶端與服務器端之間的緩存策略,它的屬性值有:

max-age

該字段指定了緩存的最大有效時間,以下爲淘寶的一張圖片:

image-20200401110102964

max-age屬性指定的時間未到期前,客戶端不會向服務器發起請求,而是從緩存中直接讀取該圖片。上圖中可以看到瀏覽器直接從ServiceWorker的緩存中讀取了該圖片資源。

Expires字段同樣可以指定緩存的有效期,不過這是HTTP1.0中的字段,優先級比HTTP1.1中的Cache-Control字段的max-age屬性低;

s-maxage

緩存設備總體來說有兩種:瀏覽器(客戶端)和CDN服務器;

  • 其中瀏覽器屬於private類型緩存設備,表示只有瀏覽器纔可以對資源進行緩存;
  • CDN服務器屬於public類型緩存設備,這種設備可以對源服務器上的資源進行緩存。並且,這種緩存對於任何用戶來說都是可以訪問的;

s-maxage的優先級在Expiresmax-age三者之中是最高的,用於指定public類型緩存設備(比如CDN)上資源的有效期。如下圖所示,該資源設定了該字段後,瀏覽器既不會使用瀏覽器緩存,也不會向服務器請求資源,而是向public類型的緩存設備(如CDN服務器)請求資源:

image-20200401131748600

private

服務器端可以通過該屬性指定某一資源只能被瀏覽器(客戶端)緩存,而不能被代理緩存服務器(CDN)緩存。

public

服務器端可以通過該屬性指定某一資源,既可以被瀏覽器緩存,也可以被代理緩存服務器緩存;

no-cache

no-cache屬性規定了瀏覽器要先向服務器端發送請求確認緩存資源的新鮮度,才能決定是否使用緩存;如下圖所示:

image-20200401112236538

no-store

該屬性指定了瀏覽器無論緩存資源是否過期直接跳過緩存,重新向服務器請求資源。no-store屬性用的比較少。

Expires字段

這是http1.0的規範;它的值爲一個絕對時間GMT(格林威治標準時間)格式時間字符串,如Mon, 10 Jun 2015 21:31:12 GMT

該字段指定了瀏覽器緩存資源的過期時間,在指定的時間到期前,瀏覽器可以直接從本地緩存中讀取數據,而無需再次向服務器發起請求,屬於強緩存;相比於max-ages-maxage優先級最低,在這兩個屬性存在的情況下Expires字段會失效;

image-20200401113310983

標識資源變化的字段

Last-Modified/If-Modified-Since

二者是基於客戶端和服務端協商的緩存機制,標識資源最後更新時間的字段。last-modified字段位於response header中,If-Modified-Since字段位於request header中,二者配合着Cache-Control字段使用。

當服務器上的資源發生改變時會同步更新last-modified的字段值,當Expires字段或max-age屬性指定的時間到期後,客戶端會在請求頭中攜帶If-Modified-Since字段,與服務器端資源的last-modified字段值進行比較:

  • 情況一:如果二者相等說明資源自last-modified字段指定的時間以後都沒有發生變化,此時服務器返回狀態碼304,屬於協商緩存
  • **情況二:**如果二者不相等說明資源發生了更新,服務器返回最新的資源和最新的last-modified字段值,此時的狀態碼爲200

舉例

下圖表示狀態碼爲304的響應:

213

  • 請求頭中If-Modified-Since字段的值爲Mon, 23 Mar 2020 18:14:15 GMT

image-20200401135630227

  • 響應頭中Last-Modified字段的值爲Mon, 23 Mar 2020 18:14:15 GMT

二者相等,說明資源沒有發生變化,所以服務器返回狀態碼304,屬於協商緩存,瀏覽器繼續使用本地緩存;

If-Modified-Since字段的值就是服務器端上一次響應資源中的Last-Modified字段值

Last-Modified的缺點

  • 一些文件也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認爲這個文件被修改了,而重新GET
  • 某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(比如淘寶每ms都會更新數據);
  • 某些服務器端不能獲取精確的修改時間;

所以有了

Etag/If-None-Match

Etag字段是HTTP1.1中的標準,是一個唯一標識服務器端資源的hash值,該字段存在於響應頭(reponse header)中;與請求頭(request header)中的If-None-Match字段及Cache-Control字段配合使用。

只要服務器端的資源發生變化Etag值就會改變,相比於Last-Modified字段優先級更高且更有效;當Expires值或者Cache-Control字段中的max-age值到期時,客戶端會在請求頭中攜帶If-None-Match字段,該字段值爲服務器端上一次響應資源中的Etag值,並與服務器端上最新資源的Etag值進行比較:

  • 情況一:如果兩個字段值相等,說明資源未發生改變,服務器端拒絕響應,返回狀態碼304,屬於協商緩存
  • **情況二:**如果兩個字段值不相等說明服務器端上的資源發生了改變,服務器在響應中返回最新的資源和Etag值,此時狀態碼爲200

舉例

下圖表示狀態碼爲304的響應:

  • 請求頭中If-None-Match字段值爲2da25d4039...

image-20200401140441351

  • 響應頭中Etag字段值爲2da25d4039...

二者相等,說明資源沒有發生變化,所以服務器返回狀態碼304,屬於協商緩存,瀏覽器繼續使用本地緩存;

總結:

  • 利用Etag能夠更加準確的控制緩存,因爲Etag是服務器自動生成或者由開發者生成的對應資源在服務器端的唯一標識符;

  • Last-ModifiedETag是可以一起使用的,由於Etag的優先度更高,所以服務器會優先比較EtagIf-None-Match。一致的情況下,纔會繼續比對Last-ModifiedIf-Modified-Since,最後才決定是否返回狀態碼304

3.緩存策略

緩存分類

  • 強緩存:

    • 不會向服務器發送請求,直接從本地緩存中獲取數;
    • 請求資源的的狀態碼爲: 200 ok(from memory cache)
  • 協商緩存:

    • 向服務器發送請求,服務器會根據請求頭的資源判斷是否命中協商緩存;
    • 如果命中,則返回狀態碼304通知瀏覽器從緩存中讀取資源;

強緩存與協商緩存的區別

緩存 獲取資源形式 狀態碼 發送請求到服務器
強緩存 從緩存中獲取 200(from cache) 否,直接從緩存中獲取
協商緩存 從緩存中獲取 304(not modified) 是,根據服務器返回信息判斷緩存是否可用

分級緩存策略

image-20200401120433734

最下層的200狀態

  • 這一層由Expires/Cache-Control字段控制:

    • 1.ExpiresHTTP1.0版本有效)是絕對時間;

    • 2.Cache-ControlHTTP1.1版本有效)是相對時間;

    當兩者都存在時,Cache-Control會覆蓋Expires,只要這些字段沒有失效,瀏覽器都會直接使用本地緩存,屬於強緩存

  • 緩存的來源大概有兩種memory cachedisk cache

image-20200401143316753

可以看到,從memory cache中讀取緩存不需要時間,從disk cache中讀取緩存則需要一定時間。

相對時間與絕對時間與服務器的設置有關,當服務器設置Atime(最後訪問時間)時,二者相等;當服務器設置Mtime(絕對修改時間)時,Expires從資源的創建開始計算過期時間,Max-age從請求發起的時間開始計算過期時間;

下圖便是淘寶中採用強緩存的例子,狀態碼爲200,圖片資源都是從瀏覽器緩存memory cache中讀取,所以請求時間爲0ms

image-20200401132234425

中間的304狀態

  • 這一層由last-modified/Etag控制。當下一層失效時或用戶點擊refresh/F5時,瀏覽器就會向服務器發起請求,如果服務器上的相關資源沒有更新,則返回狀態碼304,屬於協商緩存

下圖便爲協商緩存的情況,狀態碼爲304。也可以這樣理解:只要狀態碼是304都屬於協商緩存:

image-20200401133725298

最上層的200狀態

  • 當瀏覽器本身沒有緩存或者下一層失效時,或者用戶點擊了Ctrl + F5強制刷新時,瀏覽器會直接向服務器請求最新的資源;

如下圖所示:

image-20200401132845422

用戶行爲對緩存的影響

用戶操作 Expires/Cache-Control Last-Modified/Etag
地址欄回車 有效 有效
頁面鏈接跳轉 有效 有效
新開窗口 有效 有效
前進後退 有效 有效
F5刷新 無效 有效
Ctrl + F5強制刷新 無效 無效

緩存策略過程分析

如圖所示,該流程圖表示服務器端在處理資源時採用緩存策略的過程:

image-20200401141526275

  • 首先服務器判斷資源是否可以複用,不可複用則在Cache-Control字段中添加no-store屬性;
  • 可以複用的情況下,判斷資源是否要求強一致?若是,則在Cache-Control字段中添加no-cache屬性,這樣不管緩存資源是否過期,都要求客戶端或緩存代理服務器首先向服務器確認資源的新鮮度,屬於協商緩存
  • 隨後服務器指定是否允許Web代理緩存資源(比如CDN服務器緩存),如果允許則在Cache-Control字段中添加public屬性,並指定代理緩存服務器上資源的有效期s-maxage;不允許則添加private屬性,表示只能由客戶端瀏覽器緩存資源,並設定緩存的有效期max-age
  • 隨後,根據情況在客戶端瀏覽器中,選擇強緩存或者協商緩存;

九、服務端性能優化

1.CDN服務器

定義

網站通常將其所有的服務器都放在同一個地方,當用戶羣增加時,公司就必須在多個地理位置不同的服務器上部署內容。爲了縮短http請求的時間,我們應該把大量的靜態資源放置的離用戶近一點。

內容發佈網絡CDNContent Delivery Networks)就是其中一種方式。CDN是一組分佈在多個不同地理位置或網段的web服務器,用於更加有效的向用戶發佈內容。

image-20200402125920245

基本思路

  • 儘可能避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定;
  • 通過在網絡各處放置節點服務器,在現有的互聯網基礎之上構成一層智能虛擬網絡;
  • CDN系統能夠實時地根據網絡流量和各節點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息
    將用戶的請求重新導向離用戶最近的服務節點上。

基礎架構

最簡單的CDN網絡由一個DNS服務器和幾臺緩存服務器組成:

image-20200402133217921

  • 1、當用戶點擊網站頁面上內容的URL時,經過本地DNS系統解析,DNS系統最終會將域名的解析權交給CNAME指向的CDN專用的DNS服務器;

關於DNS解析,不一定由DNS服務器響應,一般從緩存中讀取。比如電腦緩存、瀏覽器緩存、路由器緩存、運行商緩存等。如果緩存中沒有找到,才一級一級地查詢:本地DNS-> 權限DNS -> 頂級DNS -> 根DNS。全球只有13臺根DNS服務器。

  • 2、CDNDNS服務器將CDN的全局負載均衡設備的IP地址返回給用戶;

  • 3、用戶向CDN的全局負載均衡設備發起內容URL訪問請求;

  • 4、CDN全局負載均衡設備根據用戶的IP地址,以及用戶請求的內容URL,選擇一臺用戶所屬區域的區域負載均衡設備,告訴用戶向這臺設備發起請求;

  • 5、區域負載均衡設備會爲用戶選擇一臺合適的緩存服務器提供服務,選擇的依據包括:

    • 根據用戶IP地址,判斷哪一臺服務器距用戶最近;

    • 根據用戶所請求的URL中攜帶的內容名稱,判斷哪一臺服務器上有用戶所需內容;

    • 查詢各個服務器當前的負載情況,判斷哪一臺服務器尚有服務能力;

    基於以上這些條件的綜合分析之後,CDN區域負載均衡設備會向CDN全局負載均衡設備返回一臺CDN緩存服務器的IP地址。

  • 6、CDN全局負載均衡設備把服務器的IP地址返回給用戶;

  • 7、用戶向CDN緩存服務器發起請求,緩存服務器響應用戶請求,將用戶所需內容傳送到用戶終端;如果這臺CDN緩存服務器上並沒有用戶想要的內容,但是區域均衡設備依然將它分配給了用戶,那麼這臺CDN服務器就要向它的上一級緩存服務器請求內容,直至追溯到網站的源服務器將內容拉到本地;

應用場景

  • 網站站點/應用加速:

    站點或者應用中大量靜態資源的加速分發,建議將站點內容進行動靜分離,動態文件可以結合雲服務器ECS,靜態資源如各類型圖片、htmlcssjs文件等,使用CDN服務器存儲,可以有效加速內容加載速度,輕鬆搞定網站圖片、短視頻等內容分發。

  • 移動應用加速:

    移動APP更新文件(apk文件)分發,移動APP內圖片、頁面、短視頻、UGC等內容的優化加速分發。提供httpDNS服務,避免DNS劫持並獲得實時精確的DNS解析結果,有效縮短用戶訪問時間,提升用戶體驗。

  • 視音頻點播/大文件下載分發加速;

  • 視頻直播加速;

總結

簡單點說CDN服務器相當於順豐快遞分佈於全國各地的倉庫,主倉庫將快遞運送到這些分倉庫,用戶可以就近取貨,由此加快了速度。

除此之外CDN服務器還有許多高級功能,比如防止DDOS攻擊等,這裏就不展開了;

2.SSR(Server Side Rendering)

依賴現代框架如VueReact構建的網站,往往會存在一定的問題,比如Vue框架。

Vue渲染面臨的問題

首屏渲染時,要先下載和解析app.js(打包過後的Vue.js)之後,才能開始渲染頁面。

優化方案

  • 構建層模板編譯:將模板編譯的任務放在了構建層中完成,而不是瀏覽器;
  • 數據無關的Prerender的方式;
  • 服務端渲染:即將瀏覽器端進行的運算的一部分轉移到服務器端上;

通常採用服務端渲染(SSR)的方式進行優化。所謂SSR就是利用服務器端優秀的計算能力,將一部分的頁面渲染任務交由服務器端進行處理。以下爲服務端渲染SSR的流程圖:

image-20200401165151509

服務端渲染可以很好地優化首屏渲染的問題;可以根據業務需求,適當地分配客戶端和服務器端的渲染部分,綜合利用客戶端和服務器端的計算能力,從而達到性能優化的目的。

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章