首先咱們須要經過 DNS(域名解析系統)將 URL 解析爲對應的 IP 地址,而後與這個 IP 地址肯定的那臺服務器創建起 TCP 網絡鏈接,隨後咱們向服務端拋出咱們的 HTTP 請求,服務端處理完咱們的請求以後,把目標數據放在 HTTP 響應裏返回給客戶端,拿到響應數據的瀏覽器就能夠開始走一個渲染的流程。渲染完畢,頁面便呈現給了用戶html
將這個過程切分爲以下的過程片斷前端
輸入 URL 到顯示頁面這個過程當中,涉及到網絡層面的,有三個主要過程:vue
對於 DNS 解析和 TCP 鏈接兩個步驟,咱們前端能夠作的努力很是有限。相比之下,HTTP 鏈接這一層面的優化纔是咱們網絡優化的核心webpack
HTTP 優化有兩個大的方向nginx
雪碧圖git
多張小圖片合併爲一張圖,利用CSS -background-position調整圖片顯示位置github
圖標字體文件web
阿里圖標 算法
webpack,須要斟酌而定chrome
若是圖片或者腳本,樣式文件內容比較固定,不常常被修改,那麼,儘量利用緩存技術,減小HTTP請求次數或文件下載次數
主要是減小請求中數據的大小,從而達到減小單次請求所花費的時間
若是是vue項目,還有nginx,哪麼vue,nginx,服務器都要開啓gzip
在計算機中,像素用二進制數來表示。不一樣的圖片格式中像素與二進制位數之間的對應關係是不一樣的。一個像素對應的二進制位數越多,它能夠表示的顏色種類就越多,成像效果也就越細膩,文件體積相應也會越大。
關鍵字:有損壓縮、體積小、加載快、不支持透明
JPG 最大的特色是有損壓縮。這種高效的壓縮算法使它成爲了一種很是輕巧的圖片格式。另外一方面,即便被稱爲「有損」壓縮,JPG的壓縮方式仍然是一種高質量的壓縮方式:當咱們把圖片體積壓縮至原有體積的 50% 如下時,JPG 仍然能夠保持住 60% 的品質。此外,JPG 格式以 24 位存儲單個圖,能夠呈現多達 1600 萬種顏色,足以應對大多數場景下對色彩的要求,這一點決定了它壓縮先後的質量損耗並不容易被咱們人類的肉眼所察覺——前提是你用對了業務場景。
JPG 適用於呈現色彩豐富的圖片,在咱們平常開發中,JPG 圖片常常做爲大的背景圖、輪播圖或 Banner 圖出現。
兩大電商網站對大圖的處理,是 JPG 圖片應用場景的最佳寫照:
打開淘寶首頁,咱們能夠發現頁面中最醒目、最龐大的圖片,必定是以 .jpg 爲後綴的:
使用 JPG 呈現大圖,既能夠保住圖片的質量,又不會帶來使人頭疼的圖片體積,是當下比較推崇的一種方案。
有損壓縮在上文所展現的輪播圖上確實很難露出馬腳,但當它處理矢量圖形和 Logo 等線條感較強、顏色對比強烈的圖像時,人爲壓縮致使的圖片模糊會至關明顯。
此外,JPEG 圖像不支持透明度處理,透明圖片須要召喚 PNG 來呈現。
關鍵字:無損壓縮、質量高、體積大、支持透明
PNG(可移植網絡圖形格式)是一種無損壓縮的高保真的圖片格式。8 和 24,這裏都是二進制數的位數。按照咱們前置知識裏提到的對應關係,8 位的 PNG 最多支持 256 種顏色,而 24 位的能夠呈現約 1600 萬種顏色。
PNG 圖片具備比 JPG 更強的色彩表現力,對線條的處理更加細膩,對透明度有良好的支持。它彌補了上文咱們提到的 JPG 的侷限性,惟一的 BUG 就是體積太大。
何時用 PNG-8,何時用 PNG-24,這是一個問題
理論上來講,當你追求最佳的顯示效果、而且不在乎文件體積大小時,是推薦使用 PNG-24 的。
但實踐當中,爲了規避體積的問題,咱們通常不用PNG去處理較複雜的圖像。當咱們遇到適合 PNG 的場景時,也會優先選擇更爲小巧的 PNG-8。
如何肯定一張圖片是該用 PNG-8 仍是 PNG-24 去呈現呢?好的作法是把圖片先按照這兩種格式分別輸出,看 PNG-8 輸出的結果是否會帶來肉眼可見的質量損耗,而且確認這種損耗是否在咱們(尤爲是你的 UI 設計師)可接受的範圍內,基於對比的結果去作判斷。
前面咱們提到,複雜的、色彩層次豐富的圖片,用 PNG 來處理的話,成本會比較高,咱們通常會交給 JPG 去存儲。
考慮到 PNG 在處理線條和顏色對比度方面的優點,咱們主要用它來呈現小的 Logo、顏色簡單且對比強烈的圖片或背景等。
此時咱們再次把目光轉向性能方面堪稱業界楷模的淘寶首頁,咱們會發現它頁面上的 Logo,不管大小,還真的都是 PNG 格式:
關鍵字:文本文件、體積小、不失真、兼容性好
關鍵字:文本文件、依賴編碼、小圖標解決方案
關鍵字:年輕的全能型選手
是 Google 專爲 Web 開發的一種旨在加快圖片加載速度的圖片格式,它支持有損壓縮和無損壓縮。
WebP 像 JPEG 同樣對細節豐富的圖片信手拈來,像 PNG 同樣支持透明,像 GIF 同樣能夠顯示動態圖片——它集多種圖片文件格式的優勢於一身。
兼容性
不一樣業務場景下的圖片方案選型
對於一個數據請求來講,能夠分爲發起網絡請求、後端處理、瀏覽器響應三個步驟
瀏覽器緩存能夠幫助咱們在第一和第三步驟中優化性能。好比說直接使用緩存而不發起請求,或者發起了請求但後端存儲的數據和前端一致,那麼就沒有必要再將數據回傳回來,這樣就減小了響應數據。
緩存思惟導圖
從緩存位置上來講分爲四種,而且各自有優先級,當依次查找緩存且都沒有命中的時候,纔會去請求網絡。
不瞭解
MDN
MemoryCache,是指存在內存中的緩存。從優先級上來講,它是瀏覽器最早嘗試去命中的一種緩存。從效率上來講,它是響應速度最快的一種緩存。
內存緩存是快的,也是「短命」的。它和渲染進程「生死相依」,當進程結束後,也就是 tab 關閉之後,內存裏的數據也將不復存在。
那麼哪些文件會被放入內存呢?
事實上,這個劃分規則,一直以來是沒有定論的。不過想一想也能夠理解,內存是有限的,不少時候須要先考慮即時呈現的內存餘量,再根據具體的狀況決定分配給內存和磁盤的資源量的比重——資源存放的位置具備必定的隨機性
雖然劃分規則沒有定論,但根據平常開發中觀察的結果,包括咱們開篇給你們展現的 Network 截圖,咱們至少能夠總結出這樣的規律:資源存不存內存,瀏覽器秉承的是「節約原則」。咱們發現,Base64 格式的圖片,幾乎永遠能夠被塞進 memory cache,這能夠視做瀏覽器爲節省渲染開銷的「自保行爲」;此外,體積不大的 JS、CSS 文件,也有較大地被寫入內存的概率——相比之下,較大的 JS、CSS 文件就沒有這個待遇了,內存資源是有限的,它們每每被直接甩進磁盤。
Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,可是什麼都能存儲到磁盤中,比之 Memory Cache 勝在容量和存儲時效性上。
在全部瀏覽器緩存中,Disk Cache 覆蓋面基本是最大的。它會根據 HTTP Herder 中的字段判斷哪些資源須要緩存,哪些資源能夠不請求直接使用,哪些資源已通過期須要從新請求。而且即便在跨站點的狀況下,相同地址的資源一旦被硬盤緩存下來,就不會再次去請求數據。絕大部分的緩存都來自 Disk Cache,關於 HTTP 的協議頭中的緩存字段,咱們會在下文進行詳細介紹
瀏覽器會把哪些文件丟進內存中?哪些丟進硬盤中
不瞭解
push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它纔會被使用
瀏覽器與服務器通訊的方式爲應答模式,便是:瀏覽器發起HTTP請求 – 服務器響應該請求,那麼瀏覽器怎麼肯定一個資源該不應緩存,如何去緩存呢?瀏覽器第一次向服務器發起該請求後拿到請求結果後,將請求結果和緩存標識存入瀏覽器緩存,瀏覽器對於緩存的處理是根據第一次請求資源時返回的響應頭來肯定的。具體過程以下圖:
上圖咱們能夠知道
HTTP 緩存是咱們平常開發中最爲熟悉的一種緩存機制。它又分爲強緩存和協商緩存。優先級較高的是強緩存,在命中強緩存失敗的狀況下,纔會走協商緩存。
強緩存:不會向服務器發送請求,直接從緩存中讀取資源,在chrome控制檯的Network選項中能夠看到該請求返回200的狀態碼,而且Size顯示from disk cache或from memory cache。強緩存能夠經過設置兩種 HTTP Header 實現:Expires 和 Cache-Control。
緩存過時時間,用來指定資源到期的時間,是服務器端的具體的時間點。也就是說,Expires=max-age + 請求時間,須要和Last-modified結合使用。Expires是Web服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過時時間前瀏覽器能夠直接從瀏覽器緩存取數據,而無需再次請求。
Expires 是 HTTP/1 的產物,受限於本地時間,若是修改了本地時間,可能會形成緩存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示資源會在 Wed, 22 Oct 2018 08:41:00 GMT 後過時,須要再次請求。
在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁緩存Cache-Control 能夠在請求頭或者響應頭中設置,而且能夠組合使用多種指令
max-age
max-age=xxx (xxx is numeric)表示緩存內容將在xxx秒後失效
s-maxage
private
全部內容只有客戶端能夠緩存
表示中間節點不容許緩存,對於Browser <-- proxy1 <-- proxy2 <-- Server,proxy 會老老實實把Server 返回的數據發送給proxy1,本身不緩存任何數據。當下次Browser再次請求時proxy會作好請求轉發而不是自做主張給本身緩存的數據
public
全部內容都將被緩存(客戶端和代理服務器均可緩存)
具體來講響應可被任何中間節點緩存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中間的proxy能夠緩存資源,好比下次再請求同一資源proxy1直接把本身緩存的東西給 Browser 而再也不向proxy2要。
no-store
全部內容都不會被緩存,即不使用強制緩存,也不使用協商緩存
no-cache
客戶端緩存內容,是否使用緩存則須要通過協商緩存來驗證決定。表示不使用 Cache-Control的緩存控制方式作前置驗證,而是使用 Etag 或者Last-Modified字段來控制緩存。須要注意的是,no-cache這個名字有一點誤導。設置了no-cache以後,並非說瀏覽器就再也不緩存數據,只是瀏覽器在使用緩存數據時,須要先確認一下數據是否還跟服務器保持一致
強緩存思惟導圖
協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,主要有如下兩種狀況:
協商緩存生效,返回304和Not Modified
協商緩存失效,返回200和請求結果
ast-Modified 是一個響應首部,其中包含源頭服務器認定的資源作出修改的日期及時間。 它一般被用做一個驗證器來判斷接收到的或者存儲的資源是否彼此一致。因爲精確度比 ETag 要低,因此這是一個備用機制。包含有 If-Modified-Since 或 If-Unmodified-Since 首部的條件請求會使用這個字段。
缺點
ETagHTTP響應頭是資源的特定版本的標識符。這可讓緩存更高效,並節省帶寬,由於若是內容沒有改變,Web服務器不須要發送完整的響應。而若是內容發生了變化,使用ETag有助於防止資源的同時更新相互覆蓋(「空中碰撞」)
瀏覽器緩存機制介紹與緩存策略剖析
一文讀懂前端緩存
深刻理解瀏覽器的緩存機制
生命週期:Local Storage 是持久化的本地存儲,存儲在其中的數據是永遠不會過時的,使其消失的惟一辦法是手動刪除;而 Session Storage 是臨時性的本地存儲,它是會話級別的存儲,當會話結束(頁面被關閉)時,存儲內容也隨之被釋放。
做用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特別的一點在於,即使是相同域名下的兩個頁面,只要它們不在同一個瀏覽器窗口中打開,那麼它們的 Session Storage 內容便沒法共享。
Local Storage
Session Storage
微博的 Session Storage 就主要是存儲你本次會話的瀏覽足跡
CDN (Content Delivery Network,即內容分發網絡)指的是一組分佈在各個地區的服務器。這些服務器存儲着數據的副本,所以服務器能夠根據哪些服務器與用戶距離最近,來知足數據的請求。 CDN 提供快速服務,較少受高流量影響。
緩存、本地存儲帶來的性能提高,是否是隻能在「獲取到資源並把它們存起來」這件事情發生以後?也就是說,首次請求資源的時候,這些招數都是救不了咱們的。要提高首次請求的響應能力,咱們還須要藉助 CDN 的能力
假設個人根服務器在杭州,同時在圖示的五個城市裏都有本身可用的機房
此時有一位北京的用戶向我請求資源。在網絡帶寬小、用戶訪問量大的狀況下,杭州的這一臺服務器或許不那麼給力,不能給用戶很是快的響應速度。因而我靈機一動,把這批資源 copy 了一批放在北京的機房裏。當用戶請求資源時,就近請求北京的服務器,北京這臺服務器低頭一看,這個資源我存了,離得這麼近,響應速度確定噌噌的!那若是北京這臺服務器沒有 copy 這批資源呢?它會再向杭州的根服務器去要這個資源。在這個過程當中,北京這臺服務器就扮演着 CDN 的角色。
CDN 的核心點有兩個,一個是緩存,一個是回源。
緩存
「緩存」就是說咱們把資源 copy 一份到 CDN 服務器上這個過程
回源
就是說 CDN 發現本身沒有這個資源(通常是緩存的數據過時了),轉頭向根服務器(或者它的上層服務器)去要這個資源的過程。
CDN 每每被用來存放靜態資源
「根服務器」本質上是業務服務器,它的核心任務在於生成動態頁面或返回非純靜態頁面,這兩種過程都是須要計算的。業務服務器彷彿一個車間,車間裏運轉的機器轟鳴着爲咱們產出所需的資源;相比之下,CDN 服務器則像一個倉庫,它只充當資源的「棲息地」和「搬運工」。
所謂「靜態資源」,就是像 JS、CSS、圖片等不須要業務服務器進行計算即得的資源。而「動態資源」,顧名思義是須要後端實時動態生成的資源,較爲常見的就是 JSP、ASP 或者依賴服務端渲染獲得的 HTML 頁面。
什麼是「非純靜態資源」呢?它是指須要服務器在頁面以外做額外計算的 HTML 頁面。具體來講,當我打開某一網站以前,該網站須要經過權限認證等一系列手段確認個人身份、進而決定是否要把 HTML 頁面呈現給我。這種狀況下 HTML 確實是靜態的,但它和業務服務器的操做耦合,咱們把它丟到CDN 上顯然是不合適的。
靜態資源自己具備訪問頻率高、承接流量大的特色,所以靜態資源加載速度始終是前端性能的一個很是關鍵的指標。CDN 是靜態資源提速的重要手段,在許多一線的互聯網公司,「靜態資源走 CDN」並非一個建議,而是一個規定
https://www.taobao.com/
能夠看到業務服務器確實是返回給了咱們一個還沒有被靜態資源加持過的簡單 HTML 頁面
隨便點開一個靜態資源,能夠看到它都是從 CDN 服務器上請求來的
Cookie 是緊跟域名的。同一個域名下的全部請求,都會攜帶 Cookie。你們試想,若是咱們此刻僅僅是請求一張圖片或者一個 CSS 文件,咱們也要攜帶一個 Cookie 跑來跑去(關鍵是 Cookie 裏存儲的信息我如今並不須要),這是一件多麼勞民傷財的事情。Cookie 雖然小,請求卻能夠有不少,隨着請求的疊加,這樣的沒必要要的 Cookie 帶來的開銷將是沒法想象的
同一個域名下的請求會不分青紅皁白地攜帶 Cookie,而靜態資源每每並不須要 Cookie 攜帶什麼認證信息。把靜態資源和主頁面置於不一樣的域名下,完美地避免了沒必要要的 Cookie 的出現!
看起來是一個不起眼的小細節,但帶來的效用倒是驚人的。以電商網站靜態資源的流量之龐大,若是沒把這個多餘的 Cookie 拿下來,不只用戶體驗會大打折扣,每一年因性能浪費帶來的經濟開銷也將是一個很是恐怖的數字。
客戶端渲染模式下,服務端會把渲染須要的靜態文件發送給客戶端,客戶端加載過來以後,本身在瀏覽器裏跑一遍 JS,根據 JS 的運行結果,生成相應的 DOM
<!doctype html> <html> <head> <title>我是客戶端渲染的頁面</title> </head> <body> <div id='root'></div> <script src='index.js'></script> </body> </html>
根節點下究竟是什麼內容呢?你不知道,我不知道,只有瀏覽器把 index.js 跑過一遍後才知道,這就是典型的客戶端渲染。
頁面上呈現的內容,你在 html 源文件裏裏找不到——這正是它的特色。
服務端渲染的模式下,當用戶第一次請求頁面時,由服務器把須要的組件或頁面渲染成 HTML 字符串,而後把它返回給客戶端。客戶端拿到手的,是能夠直接渲染而後呈現給用戶的 HTML 內容,不須要爲了生成 DOM 內容本身再去跑一遍 JS 代碼。
使用服務端渲染的網站,能夠說是「所見即所得」,頁面上呈現的內容,咱們在 html 源文件裏也能找到。
事實上,不少網站是出於效益(seo)的考慮才啓用服務端渲染,性能卻是在其次。
假設 A 網站頁面中有一個關鍵字叫「前端性能優化」,這個關鍵字是 JS 代碼跑過一遍後添加到 HTML 頁面中的。那麼客戶端渲染模式下,咱們在搜索引擎搜索這個關鍵字,是找不到 A 網站的——搜索引擎只會查找現成的內容,不會幫你跑 JS 代碼。A 網站的運營方見此情形,感到很頭大:搜索引擎搜不出來,用戶找不到咱們,誰還會用個人網站呢?爲了把「現成的內容」拿給搜索引擎看,A 網站不得不啓用服務端渲染。
但性能在其次,不表明性能不重要。服務端渲染解決了一個很是關鍵的性能問題——首屏加載速度過慢。在客戶端渲染模式下,咱們除了加載 HTML,還要等渲染所需的這部分 JS 加載完,以後還得把這部分 JS 在瀏覽器上再跑一遍。這一切都是發生在用戶點擊了咱們的連接以後的事情,在這個過程結束以前,用戶始終見不到咱們網頁的廬山真面目,也就是說用戶一直在等!相比之下,服務端渲染模式下,服務器給到客戶端的已是一個直接能夠拿來呈現給用戶的網頁,中間環節早在服務端就幫咱們作掉了,用戶豈不「美滋滋」?
服務端渲染本質上是本該瀏覽器作的事情,分擔給服務器去作。這樣當資源抵達瀏覽器時,它呈現的速度就快了
但仔細想一想,在這個網民遍地的時代,幾乎有多少個用戶就有多少臺瀏覽器。用戶擁有的瀏覽器總量多到數不清,那麼一個公司的服務器又有多少臺呢?咱們把這麼多臺瀏覽器的渲染壓力集中起來,分散給相比之下數量並很少的服務器,服務器確定是承受不住的
除非網頁對性能要求過高了,以致於全部的招式都用完了,性能表現仍是不盡人意,這時候咱們就能夠考慮向老闆多申請幾臺服務器,把服務端渲染搞起來了~
瀏覽器內核能夠分紅兩部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎
渲染引擎又包括了 HTML 解釋器、CSS 解釋器、佈局、網絡、存儲、圖形、音視頻、圖片解碼器等等零部件。
#myList li {}
瀏覽器必須遍歷頁面上每一個 li 元素,而且每次都要去確認這個 li 元素的父元素 id 是否是 myList
HTML、CSS 和 JS,都具備阻塞渲染的特性。
HTML 阻塞,天經地義——沒有 HTML,何來 DOM?沒有 DOM,渲染和優化,都是空談。
在剛剛的過程當中,咱們提到 DOM 和 CSSOM 協力才能構建渲染樹。這一點會給性能形成嚴重影響:默認狀況下,CSS 是阻塞的資源。瀏覽器在構建 CSSOM 的過程當中,不會渲染任何已處理的內容。即使 DOM 已經解析完畢了,只要 CSSOM 不 OK,那麼渲染這個事情就不 OK(這主要是爲了不沒有 CSS 的 HTML 頁面醜陋地「裸奔」在用戶眼前)。
只有當咱們開始解析 HTML 後、解析到 link 標籤或者 style 標籤時,CSS 才登場,CSSOM 的構建纔開始。不少時候,DOM 不得不等待 CSSOM。所以咱們能夠這樣總結:
CSS 是阻塞渲染的資源。須要將它儘早、儘快地下載到客戶端,以便縮短首次渲染的時間。
儘早(將 CSS 放在 head 標籤裏)和儘快(啓用 CDN 實現靜態資源加載速度的優化)
JS 的做用在於修改,它幫助咱們修改網頁的方方面面:內容、樣式以及它如何響應用戶交互。這「方方面面」的修改,本質上都是對 DOM 和 CSSDOM 進行修改。所以 JS 的執行會阻止 CSSOM,在咱們不做顯式聲明的狀況下,它也會阻塞 DOM。
JS 引擎是獨立於渲染引擎存在的。咱們的 JS 代碼在文檔的何處插入,就在何處執行。當 HTML 解析器遇到一個 script 標籤時,它會暫停渲染過程,將控制權交給 JS 引擎。JS 引擎對內聯的 JS 代碼會直接執行,對外部 JS 文件還要先獲取到腳本、再進行執行。等 JS 引擎運行完畢,瀏覽器又會把控制權還給渲染引擎,繼續 CSSOM 和 DOM 的構建。 所以與其說是 JS 把 CSS 和 HTML 阻塞了,不如說是 JS 引擎搶走了渲染引擎的控制權。
正常模式
這種狀況下 JS 會阻塞瀏覽器,瀏覽器必須等待 index.js 加載和執行完畢才能去作其它事情。
<script src="index.js"></script>
async(異步) 模式
async 模式下,JS 不會阻塞瀏覽器作任何其它的事情。它的加載是異步的,當它加載結束,JS 腳本會當即執行。
<script async src="index.js"></script>
defer(延緩) 模式
efer 模式下,JS 的加載是異步的,執行是被推遲的。等整個文檔解析完成、DOMContentLoaded 事件即將被觸發時,被標記了 defer 的 JS 文件纔會開始依次執行。
<script defer src="index.js"></script>
從應用的角度來講,通常當咱們的腳本與 DOM 元素和其它腳本之間的依賴關係不強時,咱們會選用 async;當腳本依賴於 DOM 元素和其它腳本的執行結果時,咱們會選用 defer。
當咱們使用 Vue 或 React 提供的接口去更新數據時,這個更新並不會當即生效,而是會被推入到一個隊列裏。待到適當的時機,隊列中的更新任務會被批量觸發。這就是異步更新。
異步更新能夠幫助咱們避免過分渲染,是咱們上節提到的「讓 JS 爲 DOM 分壓」的典範之一。
異步更新的特性在於它只看結果,所以渲染引擎不須要爲過程買單
有時咱們會遇到這樣的狀況
// 任務一 this.content = '第一次測試' // 任務二 this.content = '第二次測試' // 任務三 this.content = '第三次測試'
咱們在三個更新任務中對同一個狀態修改了三次,若是咱們採起傳統的同步更新策略,那麼就要操做三次 DOM。但本質上須要呈現給用戶的目標內容其實只是第三次的結果,也就是說只有第三次的操做是有意義的——咱們白白浪費了兩次計算。
但若是咱們把這三個任務塞進異步更新隊列裏,它們會先在 JS 的層面上被批量執行完畢。當流程走到渲染這一步時,它僅僅須要針對有意義的計算結果操做一次 DOM——這就是異步更新的妙處。
Vue在觀察到數據變化時並非直接更新DOM,而是開啓一個隊列,並緩衝在同一事件循環中發生的全部數據改變。在緩衝時會去除重複數據,從而避免沒必要要的計算和DOM操做。而後,在下一個事件循環tick中,Vue刷新隊列並執行實際工做
$nextTick就是用來知道何時DOM更新完成的
scroll 事件是一個很是容易被反覆觸發的事件。其實不止 scroll 事件,resize 事件、鼠標事件(好比 mousemove、mouseover 等)、鍵盤事件(keyup、keydown 等)都存在被頻繁觸發的風險。
頻繁觸發回調致使的大量計算會引起頁面的抖動甚至卡頓。爲了規避這種狀況,咱們須要一些手段來控制事件被觸發的頻率。就是在這樣的背景下,throttle(事件節流)和 debounce(事件防抖)出現了。
這兩個東西都以閉包的形式存在。
它們經過對事件對應的回調函數進行包裹、以自由變量的形式緩存時間信息,最後用 setTimeout 來控制事件的觸發頻率。
如今有一個旅客剛下了飛機,須要用車,因而打電話叫了該機場惟一的一輛機場大巴來接。司機開到機場,心想來都來了,多接幾我的一塊兒走吧,這樣這趟才跑得值——我等個十分鐘看看。因而司機一邊打開了計時器,一邊招呼後面的客人陸陸續續上車。在這十分鐘內,後面下飛機的乘客都只能乘這一輛大巴,十分鐘過去後,無論後面還有多少沒擠上車的乘客,這班車都必須發走
第一個乘客上車後,司機開始計時(好比說十分鐘)。十分鐘以內,若是又上來了一個乘客,司機會把計時器清零,從新開始等另外一個十分鐘(延遲了等待)。直到有這麼一位乘客,從他上車開始,後續十分鐘都沒有新乘客上車,司機會認爲確實沒有人須要搭這趟車了,纔會把車開走。
防抖 的問題在於它「太有耐心了」。試想,若是用戶的操做十分頻繁——他每次都不等 防抖 設置的 delay 時間結束就進行下一次操做,因而每次 防抖 都爲該用戶從新生成定時器,回調函數被延遲了不可勝數次。頻繁的延遲會致使用戶遲遲得不到響應,用戶一樣會產生「這個頁面卡死了」的觀感。
爲了不弄巧成拙,咱們須要借力 節流 的思想,打造一個「有底線」的 防抖——等你能夠,但我有個人原則:delay 時間內,我能夠爲你從新生成定時器;但只要delay的時間到了,我必需要給用戶一個響應。這個 節流與 防抖 「合體」思路,已經被不少成熟的前端庫應用到了它們的增強版 節流 函數的實現中
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .box { height: 2000px; background: red; } </style> </head> <body> <div class="box">11</div> <script> function throttle(fn, delay) { // last爲上一次觸發回調的時間, timer是定時器 let last = 0, timer = null // 將throttle處理結果看成函數返回 return function () { // 保留調用時的this上下文 let context = this // 保留調用時傳入的參數 let args = arguments // 記錄本次觸發回調的時間 let now = +new Date() // 判斷上次觸發的時間和本次觸發的時間差是否小於時間間隔的閾值 if (now - last < delay) { // 若是時間間隔小於咱們設定的時間間隔閾值,則爲本次觸發操做設立一個新的定時器 clearTimeout(timer) timer = setTimeout(function () { last = now fn.apply(context, args) }, delay) } else { // 若是時間間隔超出了咱們設定的時間間隔閾值,那就不等了,不管如何要反饋給用戶一次響應 last = now fn.apply(context, args) } } } // 用新的throttle包裝scroll的回調 const better_scroll = throttle(() => console.log('觸發了滾動事件'), 1000) document.addEventListener('scroll', better_scroll) </script> </body> </html>