精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

一文讀懂前端緩存

存儲(chǔ) 存儲(chǔ)軟件 前端
在國(guó)外 IT 圈和大部分國(guó)外視頻中,cache 的發(fā)音是 /kæʃ/(同 cash),這也是一個(gè)廣泛認(rèn)可的發(fā)音。但我發(fā)現(xiàn)在中國(guó)的 IT 圈還有相當(dāng)一部分程序員(比如我自己……)讀作 /kætʃ/(同 catch)。雖然不太正確,但并不妨礙互相交流。

大家都知道緩存的英文叫做 cache。但我發(fā)現(xiàn)一個(gè)有趣的現(xiàn)象:這個(gè)單詞在不同人的口中有不同的讀音。為了全面了解緩存,我們得先從讀音開(kāi)始,這樣才能夠在和其他同事(例如 PM)交(zhuāng)流(bī)時(shí)體現(xiàn)自己的修(bī)養(yǎng)(gé)。

[[244962]]

cache 怎么念

在國(guó)外 IT 圈和大部分國(guó)外視頻中,cache 的發(fā)音是 /kæʃ/(同 cash),這也是一個(gè)廣泛認(rèn)可的發(fā)音。但我發(fā)現(xiàn)在中國(guó)的 IT 圈還有相當(dāng)一部分程序員(比如我自己……)讀作 /kætʃ/(同 catch)。雖然不太正確,但并不妨礙互相交流。(不過(guò)為了純正,還是應(yīng)該向正確的方向靠攏)

此外還有一些小眾的讀法,例如 /keɪʃ/(同 kaysh),甚至 /kæʃeɪ/(像個(gè)法語(yǔ)發(fā)音,重音在后面)。這些因?yàn)樘”娏耍赡軙?huì)引起溝通障礙,估計(jì)只有在特定場(chǎng)合或者特定圈子才能順暢使用。

前端緩存/后端緩存

扯了些沒(méi)用的,我們先進(jìn)入定義環(huán)節(jié):什么是前端緩存?與之相對(duì)的什么又是后端緩存?

一文讀懂前端緩存

基本的網(wǎng)絡(luò)請(qǐng)求就是三個(gè)步驟:請(qǐng)求,處理,響應(yīng)。

后端緩存主要集中于“處理”步驟,通過(guò)保留數(shù)據(jù)庫(kù)連接,存儲(chǔ)處理結(jié)果等方式縮短處理時(shí)間,盡快進(jìn)入“響應(yīng)”步驟。當(dāng)然這不在本文的討論范圍之內(nèi)。

而前端緩存則可以在剩下的兩步:“請(qǐng)求”和“響應(yīng)”中進(jìn)行。在“請(qǐng)求”步驟中,瀏覽器也可以通過(guò)存儲(chǔ)結(jié)果的方式直接使用資源,直接省去了發(fā)送請(qǐng)求;而“響應(yīng)”步驟需要瀏覽器和服務(wù)器共同配合,通過(guò)減少響應(yīng)內(nèi)容來(lái)縮短傳輸時(shí)間。這些都會(huì)在下面進(jìn)行討論。

本文主要包含

 

  • 按緩存位置分類(lèi) (memory cache, disk cache, Service Worker 等)
  • 按失效策略分類(lèi) (Cache-Control, ETag 等)
  • 幫助理解原理的一些案例
  • 緩存的應(yīng)用模式

按緩存位置分類(lèi)

我看過(guò)的大部分討論緩存的文章會(huì)直接從 HTTP 協(xié)議頭中的緩存字段開(kāi)始,例如 Cache-Control, ETag, max-age 等。但偶爾也會(huì)聽(tīng)到別人討論 memory cache, disk cache 等。那這兩種分類(lèi)體系究竟有何關(guān)聯(lián)?是否有交叉?(我個(gè)人認(rèn)為這是本文的最大價(jià)值所在,因?yàn)樵趯?xiě)之前我自己也是被兩種分類(lèi)體系搞的一團(tuán)糟)

實(shí)際上,HTTP 協(xié)議頭的那些字段,都屬于 disk cache 的范疇,是幾個(gè)緩存位置的其中之一。因此本著從全局到局部的原則,我們應(yīng)當(dāng)先從緩存位置開(kāi)始討論。等講到 disk cache 時(shí),才會(huì)詳細(xì)講述這些協(xié)議頭的字段及其作用。

我們可以在 Chrome 的開(kāi)發(fā)者工具中,Network -> Size 一列看到一個(gè)請(qǐng)求最終的處理方式:如果是大小 (多少 K, 多少 M 等) 就表示是網(wǎng)絡(luò)請(qǐng)求,否則會(huì)列出 from memory cache, from disk cache 和 from ServiceWorker。

它們的優(yōu)先級(jí)是:(由上到下尋找,找到即返回;找不到則繼續(xù))

  • Service Worker
  • Memory Cache
  • Disk Cache
  • 網(wǎng)絡(luò)請(qǐng)求

memory cache

memory cache 是內(nèi)存中的緩存,(與之相對(duì) disk cache 就是硬盤(pán)上的緩存)。按照操作系統(tǒng)的常理:先讀內(nèi)存,再讀硬盤(pán)。disk cache 將在后面介紹 (因?yàn)樗膬?yōu)先級(jí)更低一些),這里先討論 memory cache。

幾乎所有的網(wǎng)絡(luò)請(qǐng)求資源都會(huì)被瀏覽器自動(dòng)加入到 memory cache 中。但是也正因?yàn)閿?shù)量很大但是瀏覽器占用的內(nèi)存不能無(wú)限擴(kuò)大這樣兩個(gè)因素,memory cache 注定只能是個(gè)“短期存儲(chǔ)”。常規(guī)情況下,瀏覽器的 TAB 關(guān)閉后該次瀏覽的 memory cache 便告失效 (為了給其他 TAB 騰出位置)。而如果極端情況下 (例如一個(gè)頁(yè)面的緩存就占用了超級(jí)多的內(nèi)存),那可能在 TAB 沒(méi)關(guān)閉之前,排在前面的緩存就已經(jīng)失效了。

剛才提過(guò),幾乎所有的請(qǐng)求資源 都能進(jìn)入 memory cache,這里細(xì)分一下主要有兩塊:

preloader。如果你對(duì)這個(gè)機(jī)制不太了解,這里做一個(gè)簡(jiǎn)單的介紹,詳情可以參閱這篇文章。

熟悉瀏覽器處理流程的同學(xué)們應(yīng)該了解,在瀏覽器打開(kāi)網(wǎng)頁(yè)的過(guò)程中,會(huì)先請(qǐng)求 HTML 然后解析。之后如果瀏覽器發(fā)現(xiàn)了 js, css 等需要解析和執(zhí)行的資源時(shí),它會(huì)使用 CPU 資源對(duì)它們進(jìn)行解析和執(zhí)行。在古老的年代(大約 2007 年以前),“請(qǐng)求 js/css - 解析執(zhí)行 - 請(qǐng)求下一個(gè) js/css - 解析執(zhí)行下一個(gè) js/css” 這樣的“串行”操作模式在每次打開(kāi)頁(yè)面之前進(jìn)行著。很明顯在解析執(zhí)行的時(shí)候,網(wǎng)絡(luò)請(qǐng)求是空閑的,這就有了發(fā)揮的空間:我們能不能一邊解析執(zhí)行 js/css,一邊去請(qǐng)求下一個(gè)(或下一批)資源呢?

這就是 preloader 要做的事情。不過(guò) preloader 沒(méi)有一個(gè)官方標(biāo)準(zhǔn),所以每個(gè)瀏覽器的處理都略有區(qū)別。例如有些瀏覽器還會(huì)下載 css 中的 @import 內(nèi)容或者 <video> 的 poster等。

而這些被 preloader 請(qǐng)求夠來(lái)的資源就會(huì)被放入 memory cache 中,供之后的解析執(zhí)行操作使用。

preload (雖然看上去和剛才的 preloader 就差了倆字母)。實(shí)際上這個(gè)大家應(yīng)該更加熟悉一些,例如 <link rel="preload">。這些顯式指定的預(yù)加載資源,也會(huì)被放入 memory cache 中。

memory cache 機(jī)制保證了一個(gè)頁(yè)面中如果有兩個(gè)相同的請(qǐng)求 (例如兩個(gè) src 相同的 <img>,兩個(gè) href 相同的 <link>)都實(shí)際只會(huì)被請(qǐng)求最多一次,避免浪費(fèi)。

不過(guò)在匹配緩存時(shí),除了匹配完全相同的 URL 之外,還會(huì)比對(duì)他們的類(lèi)型,CORS 中的域名規(guī)則等。因此一個(gè)作為腳本 (script) 類(lèi)型被緩存的資源是不能用在圖片 (image) 類(lèi)型的請(qǐng)求中的,即便他們 src 相等。

在從 memory cache 獲取緩存內(nèi)容時(shí),瀏覽器會(huì)忽視例如 max-age=0, no-cache 等頭部配置。例如頁(yè)面上存在幾個(gè)相同 src 的圖片,即便它們可能被設(shè)置為不緩存,但依然會(huì)從 memory cache 中讀取。這是因?yàn)?memory cache 只是短期使用,大部分情況生命周期只有一次瀏覽而已。而 max-age=0 在語(yǔ)義上普遍被解讀為“不要在下次瀏覽時(shí)使用”,所以和 memory cache 并不沖突。

但如果站長(zhǎng)是真心不想讓一個(gè)資源進(jìn)入緩存,就連短期也不行,那就需要使用 no-store。存在這個(gè)頭部配置的話,即便是 memory cache 也不會(huì)存儲(chǔ),自然也不會(huì)從中讀取了。(后面的第二個(gè)示例有關(guān)于這點(diǎn)的體現(xiàn))

disk cache

disk cache 也叫 HTTP cache,顧名思義是存儲(chǔ)在硬盤(pán)上的緩存,因此它是持久存儲(chǔ)的,是實(shí)際存在于文件系統(tǒng)中的。而且它允許相同的資源在跨會(huì)話,甚至跨站點(diǎn)的情況下使用,例如兩個(gè)站點(diǎn)都使用了同一張圖片。

disk cache 會(huì)嚴(yán)格根據(jù) HTTP 頭信息中的各類(lèi)字段來(lái)判定哪些資源可以緩存,哪些資源不可以緩存;哪些資源是仍然可用的,哪些資源是過(guò)時(shí)需要重新請(qǐng)求的。當(dāng)命中緩存之后,瀏覽器會(huì)從硬盤(pán)中讀取資源,雖然比起從內(nèi)存中讀取慢了一些,但比起網(wǎng)絡(luò)請(qǐng)求還是快了不少的。絕大部分的緩存都來(lái)自 disk cache。

關(guān)于 HTTP 的協(xié)議頭中的緩存字段,我們會(huì)在稍后進(jìn)行詳細(xì)討論。

凡是持久性存儲(chǔ)都會(huì)面臨容量增長(zhǎng)的問(wèn)題,disk cache 也不例外。在瀏覽器自動(dòng)清理時(shí),會(huì)有神秘的算法去把“最老的”或者“最可能過(guò)時(shí)的”資源刪除,因此是一個(gè)一個(gè)刪除的。不過(guò)每個(gè)瀏覽器識(shí)別“最老的”和“最可能過(guò)時(shí)的”資源的算法不盡相同,可能也是它們差異性的體現(xiàn)。

Service Worker

上述的緩存策略以及緩存/讀取/失效的動(dòng)作都是由瀏覽器內(nèi)部判斷 & 進(jìn)行的,我們只能設(shè)置響應(yīng)頭的某些字段來(lái)告訴瀏覽器,而不能自己操作。舉個(gè)生活中去銀行存/取錢(qián)的例子來(lái)說(shuō),你只能告訴銀行職員,我要存/取多少錢(qián),然后把由他們會(huì)經(jīng)過(guò)一系列的記錄和手續(xù)之后,把錢(qián)放到金庫(kù)中去,或者從金庫(kù)中取出錢(qián)來(lái)交給你。

但 Service Worker 的出現(xiàn),給予了我們另外一種更加靈活,更加直接的操作方式。依然以存/取錢(qián)為例,我們現(xiàn)在可以繞開(kāi)銀行職員,自己走到金庫(kù)前(當(dāng)然是有別于上述金庫(kù)的一個(gè)單獨(dú)的小金庫(kù)),自己把錢(qián)放進(jìn)去或者取出來(lái)。因此我們可以選擇放哪些錢(qián)(緩存哪些文件),什么情況把錢(qián)取出來(lái)(路由匹配規(guī)則),取哪些錢(qián)出來(lái)(緩存匹配并返回)。當(dāng)然現(xiàn)實(shí)中銀行沒(méi)有給我們開(kāi)放這樣的服務(wù)。

Service Worker 能夠操作的緩存是有別于瀏覽器內(nèi)部的 memory cache 或者 disk cache 的。我們可以從 Chrome 的 F12 中,Application -> Cache Storage 找到這個(gè)單獨(dú)的“小金庫(kù)”。除了位置不同之外,這個(gè)緩存是永久性的,即關(guān)閉 TAB 或者瀏覽器,下次打開(kāi)依然還在(而 memory cache 不是)。有兩種情況會(huì)導(dǎo)致這個(gè)緩存中的資源被清除:手動(dòng)調(diào)用 API cache.delete(resource) 或者容量超過(guò)限制,被瀏覽器全部清空。

如果 Service Worker 沒(méi)能命中緩存,一般情況會(huì)使用 fetch() 方法繼續(xù)獲取資源。這時(shí)候,瀏覽器就去 memory cache 或者 disk cache 進(jìn)行下一次找緩存的工作了。注意:經(jīng)過(guò) Service Worker 的 fetch() 方法獲取的資源,即便它并沒(méi)有命中 Service Worker 緩存,甚至實(shí)際走了網(wǎng)絡(luò)請(qǐng)求,也會(huì)標(biāo)注為 from ServiceWorker。這個(gè)情況在后面的第三個(gè)示例中有所體現(xiàn)。

請(qǐng)求網(wǎng)絡(luò)

如果一個(gè)請(qǐng)求在上述 3 個(gè)位置都沒(méi)有找到緩存,那么瀏覽器會(huì)正式發(fā)送網(wǎng)絡(luò)請(qǐng)求去獲取內(nèi)容。之后容易想到,為了提升之后請(qǐng)求的緩存命中率,自然要把這個(gè)資源添加到緩存中去。具體來(lái)說(shuō):

 

  • 根據(jù) Service Worker 中的 handler 決定是否存入 Cache Storage (額外的緩存位置)。
  • 根據(jù) HTTP 頭部的相關(guān)字段(Cache-control, Pragma 等)決定是否存入 disk cache
  • memory cache 保存一份資源 的引用,以備下次使用。

按失效策略分類(lèi)

memory cache 是瀏覽器為了加快讀取緩存速度而進(jìn)行的自身的優(yōu)化行為,不受開(kāi)發(fā)者控制,也不受 HTTP 協(xié)議頭的約束,算是一個(gè)黑盒。Service Worker 是由開(kāi)發(fā)者編寫(xiě)的額外的腳本,且緩存位置獨(dú)立,出現(xiàn)也較晚,使用還不算太廣泛。所以我們平時(shí)最為熟悉的其實(shí)是 disk cache,也叫 HTTP cache (因?yàn)椴幌?memory cache,它遵守 HTTP 協(xié)議頭中的字段)。平時(shí)所說(shuō)的強(qiáng)制緩存,對(duì)比緩存,以及 Cache-Control 等,也都?xì)w于此類(lèi)。

強(qiáng)制緩存 (也叫強(qiáng)緩存)

強(qiáng)制緩存的含義是,當(dāng)客戶端請(qǐng)求后,會(huì)先訪問(wèn)緩存數(shù)據(jù)庫(kù)看緩存是否存在。如果存在則直接返回;不存在則請(qǐng)求真的服務(wù)器,響應(yīng)后再寫(xiě)入緩存數(shù)據(jù)庫(kù)。

強(qiáng)制緩存直接減少請(qǐng)求數(shù),是提升最大的緩存策略。 它的優(yōu)化覆蓋了文章開(kāi)頭提到過(guò)的請(qǐng)求數(shù)據(jù)的全部三個(gè)步驟。如果考慮使用緩存來(lái)優(yōu)化網(wǎng)頁(yè)性能的話,強(qiáng)制緩存應(yīng)該是首先被考慮的。

Expires

這是 HTTP 1.0 的字段,表示緩存到期時(shí)間,是一個(gè)絕對(duì)的時(shí)間 (當(dāng)前時(shí)間+緩存時(shí)間),如

可以造成強(qiáng)制緩存的字段是 Cache-control 和 Expires。

  1. Expires: Thu, 10 Nov 2017 08:45:11 GMT 

在響應(yīng)消息頭中,設(shè)置這個(gè)字段之后,就可以告訴瀏覽器,在未過(guò)期之前不需要再次請(qǐng)求。

但是,這個(gè)字段設(shè)置時(shí)有兩個(gè)缺點(diǎn):

 

  • 由于是絕對(duì)時(shí)間,用戶可能會(huì)將客戶端本地的時(shí)間進(jìn)行修改,而導(dǎo)致瀏覽器判斷緩存失效,重新請(qǐng)求該資源。此外,即使不考慮自信修改,時(shí)差或者誤差等因素也可能造成客戶端與服務(wù)端的時(shí)間不一致,致使緩存失效。
  • 寫(xiě)法太復(fù)雜了。表示時(shí)間的字符串多個(gè)空格,少個(gè)字母,都會(huì)導(dǎo)致非法屬性從而設(shè)置失效。

Cache-control

已知Expires的缺點(diǎn)之后,在HTTP/1.1中,增加了一個(gè)字段Cache-control,該字段表示資源緩存的最大有效時(shí)間,在該時(shí)間內(nèi),客戶端不需要向服務(wù)器發(fā)送請(qǐng)求

這兩者的區(qū)別就是前者是絕對(duì)時(shí)間,而后者是相對(duì)時(shí)間。如下:

  1. Cache-control: max-age=2592000 

下面列舉一些 Cache-control 字段常用的值:(完整的列表可以查看 MDN)

 

  • max-age:即最大有效時(shí)間,在上面的例子中我們可以看到
  • must-revalidate:如果超過(guò)了 max-age 的時(shí)間,瀏覽器必須向服務(wù)器發(fā)送請(qǐng)求,驗(yàn)證資源是否還有效。
  • no-cache:雖然字面意思是“不要緩存”,但實(shí)際上還是要求客戶端緩存內(nèi)容的,只是是否使用這個(gè)內(nèi)容由后續(xù)的對(duì)比來(lái)決定。
  • no-store: 真正意義上的“不要緩存”。所有內(nèi)容都不走緩存,包括強(qiáng)制和對(duì)比。
  • public:所有的內(nèi)容都可以被緩存 (包括客戶端和代理服務(wù)器, 如 CDN)
  • private:所有的內(nèi)容只有客戶端才可以緩存,代理服務(wù)器不能緩存。默認(rèn)值。

這些值可以混合使用,例如 Cache-control:public, max-age=2592000。在混合使用時(shí),它們的優(yōu)先級(jí)如下圖:

一文讀懂前端緩存

這里有一個(gè)疑問(wèn):max-age=0 和 no-cache 等價(jià)嗎?從規(guī)范的字面意思來(lái)說(shuō),max-age 到期是 應(yīng)該(SHOULD) 重新驗(yàn)證,而 no-cache 是 必須(MUST) 重新驗(yàn)證。但實(shí)際情況以瀏覽器實(shí)現(xiàn)為準(zhǔn),大部分情況他們倆的行為還是一致的。(如果是 max-age=0, must-revalidate 就和 no-cache 等價(jià)了)

順帶一提,在 HTTP/1.1 之前,如果想使用 no-cache,通常是使用 Pragma 字段,如 Pragma: no-cache(這也是 Pragma 字段唯一的取值)。但是這個(gè)字段只是瀏覽器約定俗成的實(shí)現(xiàn),并沒(méi)有確切規(guī)范,因此缺乏可靠性。它應(yīng)該只作為一個(gè)兼容字段出現(xiàn),在當(dāng)前的網(wǎng)絡(luò)環(huán)境下其實(shí)用處已經(jīng)很小。

總結(jié)一下,自從 HTTP/1.1 開(kāi)始,Expires 逐漸被 Cache-control 取代。Cache-control 是一個(gè)相對(duì)時(shí)間,即使客戶端時(shí)間發(fā)生改變,相對(duì)時(shí)間也不會(huì)隨之改變,這樣可以保持服務(wù)器和客戶端的時(shí)間一致性。而且 Cache-control 的可配置性比較強(qiáng)大。

Cache-control 的優(yōu)先級(jí)高于 Expires,為了兼容 HTTP/1.0 和 HTTP/1.1,實(shí)際項(xiàng)目中兩個(gè)字段我們都會(huì)設(shè)置。

對(duì)比緩存 (也叫協(xié)商緩存)

當(dāng)強(qiáng)制緩存失效(超過(guò)規(guī)定時(shí)間)時(shí),就需要使用對(duì)比緩存,由服務(wù)器決定緩存內(nèi)容是否失效。

流程上說(shuō),瀏覽器先請(qǐng)求緩存數(shù)據(jù)庫(kù),返回一個(gè)緩存標(biāo)識(shí)。之后瀏覽器拿這個(gè)標(biāo)識(shí)和服務(wù)器通訊。如果緩存未失效,則返回 HTTP 狀態(tài)碼 304 表示繼續(xù)使用,于是客戶端繼續(xù)使用緩存;如果失效,則返回新的數(shù)據(jù)和緩存規(guī)則,瀏覽器響應(yīng)數(shù)據(jù)后,再把規(guī)則寫(xiě)入到緩存數(shù)據(jù)庫(kù)。

對(duì)比緩存在請(qǐng)求數(shù)上和沒(méi)有緩存是一致的,但如果是 304 的話,返回的僅僅是一個(gè)狀態(tài)碼而已,并沒(méi)有實(shí)際的文件內(nèi)容,因此 在響應(yīng)體體積上的節(jié)省是它的優(yōu)化點(diǎn)。它的優(yōu)化覆蓋了文章開(kāi)頭提到過(guò)的請(qǐng)求數(shù)據(jù)的三個(gè)步驟中的最后一個(gè):“響應(yīng)”。通過(guò)減少響應(yīng)體體積,來(lái)縮短網(wǎng)絡(luò)傳輸時(shí)間。所以和強(qiáng)制緩存相比提升幅度較小,但總比沒(méi)有緩存好。

對(duì)比緩存是可以和強(qiáng)制緩存一起使用的,作為在強(qiáng)制緩存失效后的一種后備方案。實(shí)際項(xiàng)目中他們也的確經(jīng)常一同出現(xiàn)。

對(duì)比緩存有 2 組字段(不是兩個(gè)):

Last-Modified & If-Modified-Since

1、服務(wù)器通過(guò) Last-Modified 字段告知客戶端,資源最后一次被修改的時(shí)間,例如

  1. Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT 

2、瀏覽器將這個(gè)值和內(nèi)容一起記錄在緩存數(shù)據(jù)庫(kù)中。

3、下一次請(qǐng)求相同資源時(shí)時(shí),瀏覽器從自己的緩存中找出“不確定是否過(guò)期的”緩存。因此在請(qǐng)求頭中將上次的 Last-Modified 的值寫(xiě)入到請(qǐng)求頭的 If-Modified-Since 字段

4、服務(wù)器會(huì)將 If-Modified-Since 的值與 Last-Modified 字段進(jìn)行對(duì)比。如果相等,則表示未修改,響應(yīng) 304;反之,則表示修改了,響應(yīng) 200 狀態(tài)碼,并返回?cái)?shù)據(jù)。

但是他還是有一定缺陷的:

 

  • 如果資源更新的速度是秒以下單位,那么該緩存是不能被使用的,因?yàn)樗臅r(shí)間單位最低是秒。
  • 如果文件是通過(guò)服務(wù)器動(dòng)態(tài)生成的,那么該方法的更新時(shí)間永遠(yuǎn)是生成的時(shí)間,盡管文件可能沒(méi)有變化,所以起不到緩存的作用。

Etag & If-None-Match

為了解決上述問(wèn)題,出現(xiàn)了一組新的字段 Etag 和 If-None-Match

Etag 存儲(chǔ)的是文件的特殊標(biāo)識(shí)(一般都是 hash 生成的),服務(wù)器存儲(chǔ)著文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新時(shí)間改變成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 變成了 If-None-Match。服務(wù)器同樣進(jìn)行比較,命中返回 304, 不命中返回新資源和 200。

Etag 的優(yōu)先級(jí)高于 Last-Modified

緩存小結(jié)

當(dāng)瀏覽器要請(qǐng)求資源時(shí)

 

  • 調(diào)用 Service Worker 的 fetch 事件響應(yīng)
  • 查看 memory cache
  • 查看 disk cache。這里又細(xì)分:
  • 如果有強(qiáng)制緩存且未失效,則使用強(qiáng)制緩存,不請(qǐng)求服務(wù)器。這時(shí)的狀態(tài)碼全部是 200
  • 如果有強(qiáng)制緩存但已失效,使用對(duì)比緩存,比較后確定 304 還是 200
  • 發(fā)送網(wǎng)絡(luò)請(qǐng)求,等待網(wǎng)絡(luò)響應(yīng)
  • 把響應(yīng)內(nèi)容存入 disk cache (如果 HTTP 頭信息配置可以存的話)
  • 把響應(yīng)內(nèi)容 的引用 存入 memory cache (無(wú)視 HTTP 頭信息的配置)
  • 把響應(yīng)內(nèi)容存入 Service Worker 的 Cache Storage (如果 Service Worker 的腳本調(diào)用了 cache.put())

一些案例

光看原理不免枯燥。我們編寫(xiě)一些簡(jiǎn)單的網(wǎng)頁(yè),通過(guò)案例來(lái)深刻理解上面的那些原理。

1、memory cache & disk cache

我們寫(xiě)一個(gè)簡(jiǎn)單的 index.html,然后引用 3 種資源,分別是 index.js, index.css 和 mashroom.jpg。

我們給這三種資源都設(shè)置上 Cache-control: max-age=86400,表示強(qiáng)制緩存 24 小時(shí)。以下截圖全部使用 Chrome 的隱身模式。

首次請(qǐng)求

毫無(wú)意外的全部走網(wǎng)絡(luò)請(qǐng)求,因?yàn)槭裁淳彺娑歼€沒(méi)有。

再次請(qǐng)求 (F5)

第二次請(qǐng)求,三個(gè)請(qǐng)求都來(lái)自 memory cache。因?yàn)槲覀儧](méi)有關(guān)閉 TAB,所以瀏覽器把緩存的應(yīng)用加到了 memory cache。(耗時(shí) 0ms,也就是 1ms 以內(nèi))

關(guān)閉 TAB,打開(kāi)新 TAB 并再次請(qǐng)求

因?yàn)殛P(guān)閉了 TAB,memory cache 也隨之清空。但是 disk cache 是持久的,于是所有資源來(lái)自 disk cache。(大約耗時(shí) 3ms,因?yàn)槲募悬c(diǎn)小)

而且對(duì)比 2 和 3,很明顯看到 memory cache 還是比 disk cache 快得多的。

2、no-cache & no-store

我們?cè)?index.html 里面一些代碼,完成兩個(gè)目標(biāo):

 

  • 每種資源都(同步)請(qǐng)求兩次
  • 增加腳本異步請(qǐng)求圖片
  1. <!-- 把3種資源都改成請(qǐng)求兩次 --> 
  2. <link rel="stylesheet" href="/static/index.css"
  3. <link rel="stylesheet" href="/static/index.css"
  4. <script src="/static/index.js"></script> 
  5. <script src="/static/index.js"></script> 
  6. <img src="/static/mashroom.jpg"
  7. <img src="/static/mashroom.jpg"
  8. <!-- 異步請(qǐng)求圖片 --> 
  9. <script> 
  10.  setTimeout(function () { 
  11.  let img = document.createElement('img'
  12.  img.src = '/static/mashroom.jpg' 
  13.  document.body.appendChild(img) 
  14.  }, 1000) 
  15. </script> 

當(dāng)把服務(wù)器響應(yīng)設(shè)置為 Cache-Control: no-cache 時(shí),我們發(fā)現(xiàn)打開(kāi)頁(yè)面之后,三種資源都只被請(qǐng)求 1 次。

 

一文讀懂前端緩存

這說(shuō)明兩個(gè)問(wèn)題:

  • 同步請(qǐng)求方面,瀏覽器會(huì)自動(dòng)把當(dāng)次 HTML 中的資源存入到緩存 (memory cache),這樣碰到相同 src 的圖片就會(huì)自動(dòng)讀取緩存(但不會(huì)在 Network 中顯示出來(lái))
  • 異步請(qǐng)求方面,瀏覽器同樣是不發(fā)請(qǐng)求而直接讀取緩存返回。但同樣不會(huì)在 Network 中顯示。

總體來(lái)說(shuō),如上面原理所述,no-cache 從語(yǔ)義上表示下次請(qǐng)求不要直接使用緩存而需要比對(duì),并不對(duì)本次請(qǐng)求進(jìn)行限制。因此瀏覽器在處理當(dāng)前頁(yè)面時(shí),可以放心使用緩存。

當(dāng)把服務(wù)器響應(yīng)設(shè)置為 Cache-Control: no-store 時(shí),情況發(fā)生了變化,三種資源都被請(qǐng)求了 2 次。而圖片因?yàn)檫€多一次異步請(qǐng)求,總計(jì) 3 次。(紅框中的都是那一次異步請(qǐng)求)

一文讀懂前端緩存

 

一文讀懂前端緩存

 

 

這同樣說(shuō)明:

 

  • 如之前原理所述,雖然 memory cache 是無(wú)視 HTTP 頭信息的,但是 no-store 是特別的。在這個(gè)設(shè)置下,memory cache 也不得不每次都請(qǐng)求資源。
  • 異步請(qǐng)求和同步遵循相同的規(guī)則,在 no-store 情況下,依然是每次都發(fā)送請(qǐng)求,不進(jìn)行任何緩存。

3、Service Worker & memory (disk) cache

我們嘗試把 Service Worker 也加入進(jìn)去。我們編寫(xiě)一個(gè) serviceWorker.js,并編寫(xiě)如下內(nèi)容:(主要是預(yù)緩存 3 個(gè)資源,并在實(shí)際請(qǐng)求時(shí)匹配緩存并返回)

  1. // serviceWorker.js 
  2. self.addEventListener('install', e => { 
  3.  // 當(dāng)確定要訪問(wèn)某些資源時(shí),提前請(qǐng)求并添加到緩存中。 
  4.  // 這個(gè)模式叫做“預(yù)緩存” 
  5.  e.waitUntil( 
  6.  caches.open('service-worker-test-precache').then(cache => { 
  7.  return cache.addAll(['/static/index.js''/static/index.css''/static/mashroom.jpg']) 
  8.  }) 
  9.  ) 
  10. }) 
  11. self.addEventListener('fetch', e => { 
  12.  // 緩存中能找到就返回,找不到就網(wǎng)絡(luò)請(qǐng)求,之后再寫(xiě)入緩存并返回。 
  13.  // 這個(gè)稱(chēng)為 CacheFirst 的緩存策略。 
  14.  return e.respondWith( 
  15.  caches.open('service-worker-test-precache').then(cache => { 
  16.  return cache.match(e.request).then(matchedResponse => { 
  17.  return matchedResponse || fetch(e.request).then(fetchedResponse => { 
  18.  cache.put(e.request, fetchedResponse.clone()) 
  19.  return fetchedResponse 
  20.  }) 
  21.  }) 
  22.  }) 
  23.  ) 
  24. }) 

 

注冊(cè) SW 的代碼這里就不贅述了。此外我們還給服務(wù)器設(shè)置 Cache-Control: max-age=86400 來(lái)開(kāi)啟 disk cache。我們的目的是看看兩者的優(yōu)先級(jí)。

當(dāng)我們首次訪問(wèn)時(shí),會(huì)看到常規(guī)請(qǐng)求之外,瀏覽器(確切地說(shuō)是 Service Worker)額外發(fā)出了 3 個(gè)請(qǐng)求。這來(lái)自預(yù)緩存的代碼。

第二次訪問(wèn)(無(wú)論關(guān)閉 TAB 重新打開(kāi),還是直接按 F5 刷新)都能看到所有的請(qǐng)求標(biāo)記為 from SerciceWorker。

from ServiceWorker 只表示請(qǐng)求通過(guò)了 Service Worker,至于到底是命中了緩存,還是繼續(xù) fetch() 方法光看這一條記錄其實(shí)無(wú)從知曉。因此我們還得配合后續(xù)的

Network 記錄來(lái)看。因?yàn)橹鬀](méi)有額外的請(qǐng)求了,因此判定是命中了緩存。

一文讀懂前端緩存

 

 

從服務(wù)器的日志也能很明顯地看到,3 個(gè)資源都沒(méi)有被重新請(qǐng)求,即命中了 Service Worker 內(nèi)部的緩存。

如果修改 serviceWorker.js 的 fetch 事件監(jiān)聽(tīng)代碼,改為如下:

  1. // 這個(gè)也叫做 NetworkOnly 的緩存策略。 
  2. self.addEventListener('fetch', e => { 
  3. return e.respondWith(fetch(e.request)) 
  4. }) 

可以發(fā)現(xiàn)在后續(xù)訪問(wèn)時(shí)的效果和修改前是 完全一致的。(即 Network 僅有標(biāo)記為 from ServiceWorker 的幾個(gè)請(qǐng)求,而服務(wù)器也不打印 3 個(gè)資源的訪問(wèn)日志)

很明顯 Service Worker 這層并沒(méi)有去讀取自己的緩存,而是直接使用 fetch() 進(jìn)行請(qǐng)求。所以此時(shí)其實(shí)是 Cache-Control: max-age=86400 的設(shè)置起了作用,也就是 memory/disk cache。但具體是 memory 還是 disk 這個(gè)只有瀏覽器自己知道了,因?yàn)樗](méi)有顯式的告訴我們。(個(gè)人猜測(cè)是 memory,因?yàn)椴徽搹暮臅r(shí) 0ms 還是從不關(guān)閉 TAB 來(lái)看,都更像是 memory cache)

瀏覽器的行為

所謂瀏覽器的行為,指的就是用戶在瀏覽器如何操作時(shí),會(huì)觸發(fā)怎樣的緩存策略。主要有 3 種:

 

  • 打開(kāi)網(wǎng)頁(yè),地址欄輸入地址: 查找 disk cache 中是否有匹配。如有則使用;如沒(méi)有則發(fā)送網(wǎng)絡(luò)請(qǐng)求。
  • 普通刷新 (F5):因?yàn)?TAB 并沒(méi)有關(guān)閉,因此 memory cache 是可用的,會(huì)被優(yōu)先使用(如果匹配的話)。其次才是 disk cache。
  • 強(qiáng)制刷新 (Ctrl + F5):瀏覽器不使用緩存,因此發(fā)送的請(qǐng)求頭部均帶有 Cache-control: no-cache(為了兼容,還帶了 Pragma: no-cache)。服務(wù)器直接返回 200 和最新內(nèi)容。

緩存的應(yīng)用模式

了解了緩存的原理,我們可能更加關(guān)心如何在實(shí)際項(xiàng)目中使用它們,才能更好的讓用戶縮短加載時(shí)間,節(jié)約流量等。這里有幾個(gè)常用的模式,供大家參考

模式 1:不常變化的資源

  1. Cache-Control: max-age=31536000 

通常在處理這類(lèi)資源資源時(shí),給它們的 Cache-Control 配置一個(gè)很大的 max-age=31536000 (一年),這樣瀏覽器之后請(qǐng)求相同的 URL 會(huì)命中強(qiáng)制緩存。而為了解決更新的問(wèn)題,就需要在文件名(或者路徑)中添加 hash, 版本號(hào)等動(dòng)態(tài)字符,之后更改動(dòng)態(tài)字符,達(dá)到更改引用 URL 的目的,從而讓之前的強(qiáng)制緩存失效 (其實(shí)并未立即失效,只是不再使用了而已)。

在線提供的類(lèi)庫(kù) (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用這個(gè)模式。如果配置中還增加 public 的話,CDN 也可以緩存起來(lái),效果拔群。

這個(gè)模式的一個(gè)變體是在引用 URL 后面添加參數(shù) (例如 ?v=xxx 或者 ?_=xxx),這樣就不必在文件名或者路徑中包含動(dòng)態(tài)參數(shù),滿足某些完美主義者的喜好。在項(xiàng)目每次構(gòu)建時(shí),更新額外的參數(shù) (例如設(shè)置為構(gòu)建時(shí)的當(dāng)前時(shí)間),則能保證每次構(gòu)建后總能讓瀏覽器請(qǐng)求最新的內(nèi)容。

特別注意: 在處理 Service Worker 時(shí),對(duì)待 sw-register.js(注冊(cè) Service Worker) 和 serviceWorker.js (Service Worker 本身) 需要格外的謹(jǐn)慎。如果這兩個(gè)文件也使用這種模式,你必須多多考慮日后可能的更新及對(duì)策。

模式 2:經(jīng)常變化的資源

  1. Cache-Control: no-cache 

這里的資源不單單指靜態(tài)資源,也可能是網(wǎng)頁(yè)資源,例如博客文章。這類(lèi)資源的特點(diǎn)是:URL 不能變化,但內(nèi)容可以(且經(jīng)常)變化。我們可以設(shè)置 Cache-Control: no-cache 來(lái)迫使瀏覽器每次請(qǐng)求都必須找服務(wù)器驗(yàn)證資源是否有效。

既然提到了驗(yàn)證,就必須 ETag 或者 Last-Modified 出場(chǎng)。這些字段都會(huì)由專(zhuān)門(mén)處理靜態(tài)資源的常用類(lèi)庫(kù)(例如 koa-static)自動(dòng)添加,無(wú)需開(kāi)發(fā)者過(guò)多關(guān)心。

也正如上文中提到協(xié)商緩存那樣,這種模式下,節(jié)省的并不是請(qǐng)求數(shù),而是請(qǐng)求體的大小。所以它的優(yōu)化效果不如模式 1 來(lái)的顯著。

模式 3:非常危險(xiǎn)的模式 1 和 2 的結(jié)合 (反例)

  1. Cache-Control: max-age=600, must-revalidate 

不知道是否有開(kāi)發(fā)者從模式 1 和 2 獲得一些啟發(fā):模式 2 中,設(shè)置了 no-cache,相當(dāng)于 max-age=0, must-revalidate。我的應(yīng)用時(shí)效性沒(méi)有那么強(qiáng),但又不想做過(guò)于長(zhǎng)久的強(qiáng)制緩存,我能不能配置例如 max-age=600, must-revalidate 這樣折中的設(shè)置呢?

表面上看這很美好:資源可以緩存 10 分鐘,10 分鐘內(nèi)讀取緩存,10 分鐘后和服務(wù)器進(jìn)行一次驗(yàn)證,集兩種模式之大成,但實(shí)際線上暗存風(fēng)險(xiǎn)。因?yàn)樯厦嫣徇^(guò),瀏覽器的緩存有自動(dòng)清理機(jī)制,開(kāi)發(fā)者并不能控制。

舉個(gè)例子:當(dāng)我們有 3 種資源: index.html, index.js, index.css。我們對(duì)這 3 者進(jìn)行上述配置之后,假設(shè)在某次訪問(wèn)時(shí),index.js 已經(jīng)被緩存清理而不存在,但 index.html, index.css 仍然存在于緩存中。這時(shí)候?yàn)g覽器會(huì)向服務(wù)器請(qǐng)求新的 index.js,然后配上老的 index.html, index.css 展現(xiàn)給用戶。這其中的風(fēng)險(xiǎn)顯而易見(jiàn):不同版本的資源組合在一起,報(bào)錯(cuò)是極有可能的結(jié)局。

除了自動(dòng)清理引發(fā)問(wèn)題,不同資源的請(qǐng)求時(shí)間不同也能導(dǎo)致問(wèn)題。例如 A 頁(yè)面請(qǐng)求的是 A.js 和 all.css,而 B 頁(yè)面是 B.js 和 all.css。如果我們以 A -> B 的順序訪問(wèn)頁(yè)面,勢(shì)必導(dǎo)致 all.css 的緩存時(shí)間早于 B.js。那么以后訪問(wèn) B 頁(yè)面就同樣存在資源版本失配的隱患。

后記

這篇文章真心有點(diǎn)長(zhǎng),但已經(jīng)囊括了前端緩存的絕大部分,包括 HTTP 協(xié)議中的緩存,Service Worker,以及 Chrome 瀏覽器的一些優(yōu)化 (Memory Cache)。希望開(kāi)發(fā)者們善用緩存,因?yàn)樗亲钊菀紫氲剑嵘沧畲蟮男阅軆?yōu)化策略。

參考文章

A Tale of Four Caches(但這篇文章把 Service Worker 的優(yōu)先級(jí)排在 memory cache 和 disk cache 之間,跟我實(shí)驗(yàn)效果并不相符。懷疑可能是 2 年來(lái) chrome 策略的修改?)

Caching best practices & max-age gotchas

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2021-05-18 09:48:58

前端開(kāi)發(fā)架構(gòu)

2023-11-21 09:41:00

緩存策略存儲(chǔ)

2020-12-29 09:56:29

瀏覽器緩存HTTP

2021-08-04 16:06:45

DataOps智領(lǐng)云

2023-12-22 19:59:15

2022-09-22 09:00:46

CSS單位

2022-11-06 21:14:02

數(shù)據(jù)驅(qū)動(dòng)架構(gòu)數(shù)據(jù)

2025-04-03 10:56:47

2023-11-27 17:35:48

ComponentWeb外層

2022-07-05 06:30:54

云網(wǎng)絡(luò)網(wǎng)絡(luò)云原生

2023-05-20 17:58:31

低代碼軟件

2022-10-20 08:01:23

2025-10-14 09:01:20

2022-12-01 17:23:45

2021-12-29 18:00:19

無(wú)損網(wǎng)絡(luò)網(wǎng)絡(luò)通信網(wǎng)絡(luò)

2022-07-26 00:00:03

語(yǔ)言模型人工智能

2017-05-04 20:29:12

HTTP服務(wù)器TCP

2025-05-20 11:55:22

人工智能Vision RAGLLM

2024-01-03 08:54:17

Kubernetes策略工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美大片xxxx| 欧美少妇性生活视频| 欧美 日韩 国产 精品| 国产欧美精品| 主播福利视频一区| 久久久久99人妻一区二区三区| 麻豆mv在线看| 黄网站在线观| 激情视频一区| 在线观看国产精品淫| 国产成人精品综合久久久久99| 在线观看特色大片免费视频| 亚洲男人天堂av网| 视频在线99| 视频污在线观看| 精久久久久久久久久久| 日韩免费观看高清| 国产午夜小视频| 香蕉久久网站| 中文字幕亚洲图片| 这里只有久久精品| 欧美久久精品| 欧美精品一区二区三区很污很色的 | 成人毛片视频免费看| www欧美在线观看| 欧美性猛交xxxx偷拍洗澡 | 好操啊在线观看免费视频| 欧美亚洲不卡| 最新亚洲国产精品| 精品国产免费久久久久久婷婷| 国产综合色在线观看| 精品动漫一区二区三区| 国产女人18毛片| 思思99re6国产在线播放| 高清一区二区视频| 欧美69xxxxx| 成熟亚洲日本毛茸茸凸凹| 国产日产亚洲精品| 在线免费一区二区| 国产原厂视频在线观看| 日本一区二区不卡视频| 日韩激情视频| 九色视频在线观看免费播放| 久久综合九色综合97婷婷女人| 亚洲成年人网站在线观看| 欧美激情一区二区三区在线视频| 在线成人免费视频| 五月天亚洲综合小说网| 久久综合九色综合久| 99视频精品在线| 岛国一区二区三区高清视频| 99视频精品免费| 91豆花视频在线播放| 亚洲欧美日韩国产综合在线| 亚洲一区二区三区精品在线观看| 电影在线高清| 国产精品久久久久久久久免费樱桃 | 国产成人午夜高潮毛片| yellow视频在线观看一区二区| 国产麻豆精品一区| 国产精品正在播放| 91青青草免费观看| 亚洲黄色片视频| 波多野结衣中文字幕一区| 久久久久久九九九九| 男生女生差差差的视频在线观看| 国产精品色在线观看| 91久久国产精品| 国产深喉视频一区二区| 国产v日产∨综合v精品视频| 欧美精品xxxxbbbb| 深爱五月综合网| 高潮久久久久久久久久久久久久| 欧美一区二区三区视频免费播放 | av网站在线免费看| 青青草97国产精品免费观看无弹窗版| 国产精品女主播| 97av自拍| 国产精品sm调教免费专区| 日本女人一区二区三区| 成人久久一区二区| 男人天堂手机在线观看| 久久久不卡影院| 韩国黄色一级大片| 韩日毛片在线观看| 欧洲国内综合视频| 中文字幕在线观看视频www| 日韩福利视频一区| 日韩中文字幕网| 免费日韩一级片| 美女任你摸久久| 国产视频不卡| 女女色综合影院| 婷婷亚洲久悠悠色悠在线播放| 亚洲欧美在线精品| 国产精品极品国产中出| 中文字幕亚洲欧美在线 | 九色在线观看| 亚洲精品高清视频在线观看| 国产麻花豆剧传媒精品mv在线| 伊人久久大香| 亚洲欧美日韩精品| 久草资源在线视频| 青青青伊人色综合久久| 国产一区喷水| av片在线观看永久免费| 在线视频你懂得一区| 亚洲成a人无码| 色综合天天综合网中文字幕| 青青久久av北条麻妃黑人| www.av导航| 国产精品色一区二区三区| www国产精品内射老熟女| 91精品网站在线观看| 亚洲色图av在线| 日本熟女一区二区| 国产精品亚洲第一区在线暖暖韩国| 欧美在线视频一区二区三区| av资源网在线播放| 日韩欧美中文字幕一区| 亚洲天堂最新地址| 三级一区在线视频先锋| 韩国成人av| 国产偷倩在线播放| 欧美一级黄色片| 日本二区三区视频| 琪琪一区二区三区| 色噜噜一区二区| 91精品论坛| 精品视频久久久久久久| 一级aaa毛片| 成人午夜视频网站| 欧美 亚洲 视频| 激情不卡一区二区三区视频在线| 色综合影院在线| 中文字幕乱码无码人妻系列蜜桃| 久久精品人人做人人综合| 成人在线免费观看av| 免费成人三级| 欧美在线亚洲在线| 色就是色亚洲色图| 色av综合在线| 久久精品欧美| 国产黄大片在线观看| 欧美精品一区二区三区久久久| 青青草手机在线视频| 国产传媒日韩欧美成人| 成人免费观看在线| 狼人天天伊人久久| 97精品视频在线| 日韩av成人| 在线视频国内一区二区| 少妇太紧太爽又黄又硬又爽小说| 热久久久久久久| 中文字幕精品—区二区日日骚| 欧美成人毛片| 欧美大胆在线视频| 免费看黄网站在线观看| 欧美午夜视频一区二区| 亚欧精品视频一区二区三区| 久久国产成人午夜av影院| 中文字幕在线乱| jizz性欧美23| 2020欧美日韩在线视频| 国产精品一区二区婷婷| 欧美日韩国产综合一区二区三区 | 成人久久久久久久| 日本无删减在线| 日韩av在线看| 国产99在线播放| 亚洲精品.www| 精品久久久中文| 欧美一区二区三区粗大| 国产又黄又大久久| 两根大肉大捧一进一出好爽视频| 国产精品一在线观看| 亚洲影视九九影院在线观看| 92久久精品| 中文字幕亚洲第一| 日本精品一区二区在线观看| 欧美三级xxx| 成人18视频免费69| 国产成人精品一区二区三区网站观看| 好吊妞无缓冲视频观看| 色喇叭免费久久综合网| 俄罗斯精品一区二区三区| 蜜桃av在线| 日韩综合中文字幕| 亚洲av成人精品日韩在线播放| 欧美亚洲日本一区| 国产真实的和子乱拍在线观看| 国产午夜亚洲精品不卡| 韩国三级丰满少妇高潮| av在线二区| 美女免费视频一区| 日韩精品一区二区免费| 国产欧美日韩视频在线| 91免费在线视频| 涩涩涩在线视频| 久久躁狠狠躁夜夜爽| 国产中文在线观看| 精品少妇一区二区三区在线播放| 久久久蜜桃一区二区| 日韩在线你懂得| 日韩一区视频在线| 亚洲AV第二区国产精品| 69精品人人人人| 欧美理论电影在线观看| 中文字字幕在线中文乱码| 亚洲自拍偷拍九九九| 黄色av免费播放| av男人天堂一区| 欧美精品色视频| 男人操女人的视频在线观看欧美| 国产精品无码人妻一区二区在线| 88国产精品视频一区二区三区| 日产精品久久久一区二区| 国产欧美啪啪| 99在线视频播放| 亚洲成人毛片| 国产精品无av码在线观看| 91搞黄在线观看| 欧美丰满熟妇bbb久久久| 蜜桃久久久久久| 99re在线视频免费观看| 亚洲高清免费| 9色porny| 久久精品人人| 久久精品国产久精国产一老狼| 欧美少妇bbw| 91精品国产综合久久精品性色| www.久久久久久久| 亚洲不卡在线观看| 深夜福利久久| 久久精品91久久香蕉加勒比| 黄色av免费在线看| 亚洲精品综合精品自拍| 天天射天天操天天干| 精品久久久三级丝袜| 成人免费公开视频| 欧美成人免费网站| 亚洲a视频在线观看| 精品日韩在线观看| 动漫av一区二区三区| 亚洲精品在线一区二区| 欧美视频xxx| 日韩精品中文字幕有码专区| 日本一区高清| 一本色道久久综合亚洲精品小说| 丝袜视频国产在线播放| 亚洲美腿欧美激情另类| 国产精品一二三区视频| 日日骚av一区| av中文字幕在线播放| 欧美日本精品在线| 91破解版在线观看| 日本一区二区不卡| 国产一区二区三区四区五区3d| 国产精品中文在线| 成人精品在线| 国产精品swag| 妖精视频一区二区三区免费观看| 日本在线观看一区二区| 日韩中文在线电影| 成人免费看片视频在线观看| 欧美日一区二区在线观看 | 丰满熟女人妻一区二区三区| 色哟哟免费在线观看| 亚洲欧洲高清在线| 午夜国产福利在线| 色综合久久久久久中文网| 欧美久久天堂| 国产精品丝袜高跟| 亚洲精品观看| 老司机精品久久| www.国产区| 极品少妇一区二区| 扒开伸进免费视频| 亚洲国产精品t66y| 青青草在线观看视频| 欧美日韩亚洲视频| 国产又大又长又粗| 亚洲国产成人在线播放| 在线观看麻豆蜜桃| 久久久久久九九九| 成人涩涩视频| 国产91亚洲精品一区二区三区| 中国av一区| 五月天激情图片| 天堂va蜜桃一区二区三区漫画版| 在线播放免费视频| 91免费在线播放| 三级av在线免费观看| 欧美日韩中文字幕在线| 国产精品久久久午夜夜伦鲁鲁| 日韩av在线免费播放| 国产黄a三级三级三级av在线看 | 看黄色一级大片| 欧美成人一区二区三区在线观看 | 成年人在线观看视频免费| 国产精品538一区二区在线| 白丝女仆被免费网站| 亚洲精品一二三区| 亚洲一区中文字幕永久在线| 亚洲精品理论电影| 在线观看操人| 国产专区精品视频| 精品久久综合| 国产二区视频在线播放| 国产精品亚洲一区二区三区妖精 | 男生女生差差差的视频在线观看| 欧美精品999| 国产精品国产亚洲精品| 少妇免费毛片久久久久久久久| 岛国一区二区| 日韩精品一区二区在线观看| 国产视频三级在线观看播放| 久久久久女教师免费一区| 99久热在线精品视频观看| 日韩av电影免费观看| 亚洲中字在线| 伊人网综合视频| 亚洲综合清纯丝袜自拍| 国产美女三级无套内谢| 日韩中文在线不卡| 国产伊人久久| 亚洲三区在线观看| 免费在线看成人av| 国产精成人品免费观看| 在线看国产一区| 国产小视频在线| 日韩免费观看在线观看| 香蕉久久夜色精品国产使用方法| 国产日韩av网站| 不卡大黄网站免费看| 欧美一级视频免费观看| 亚洲成人精品视频| 理论不卡电影大全神| 精品视频免费观看| 免费日韩av片| x88av在线| 欧美日韩视频专区在线播放| 午夜老司机在线观看| 91九色视频导航| 牛牛国产精品| 久久无码专区国产精品s| 亚洲午夜免费电影| 人妻无码中文字幕| 97av在线视频| 国产欧美亚洲精品a| 欧美三级午夜理伦三级富婆| 国产精品欧美久久久久一区二区| 91麻豆视频在线观看| 久久福利视频导航| 91综合精品国产丝袜长腿久久| 黄色一级片在线看| 久久久久久电影| 这里只有精品9| 久久综合五月天| 欧美成人专区| 97av视频在线| 最近中文字幕在线观看| 日韩成人av一区| 欧美123区| 国产三级中文字幕| 成人性色生活片| 黄色污污网站在线观看| www.亚洲成人| 盗摄系列偷拍视频精品tp| 18岁视频在线观看| 中文字幕一区二区在线观看| www.天堂在线| 秋霞av国产精品一区| 99久久99热这里只有精品| 日韩精品xxx| 在线这里只有精品| 在线观看的网站你懂的| 久久另类ts人妖一区二区| 美腿丝袜亚洲一区| 久久久全国免费视频| 伊人伊成久久人综合网站| 欧洲大片精品免费永久看nba| 国产视频九色蝌蚪| 国产精品久久网站| 熟妇人妻中文av无码| 国产免费久久av| 亚洲三级免费| 91久久久久久久久久久久久久 | 亚洲乱码av中文一区二区| 免费福利视频一区二区三区| 国产系列第一页| 99re这里只有精品首页| 91在线你懂的| 欧美性做爰毛片| 欧美在线1区| jizz中文字幕| 亚洲国产精品久久久久秋霞不卡| 久久影视精品| 欧美综合在线观看视频| 亚洲成a人v欧美综合天堂|