一、緩存
1. 緩存發(fā)展的歷史
在前端開發(fā)中,性能一直都是被大家所重視的一點(diǎn)邦危,然而判斷一個(gè)網(wǎng)站的性能最直觀的就是看網(wǎng)頁打開的速度诽凌。其中提高網(wǎng)頁反應(yīng)速度的一個(gè)方式就是使用緩存毡熏。緩存技術(shù)一直一來在WEB技術(shù)體系中扮演非常重要角色,是快速且有效地提升性能的手段侣诵。
一個(gè)優(yōu)秀的緩存策略可以縮短網(wǎng)頁請求資源的距離痢法,減少延遲,并且由于緩存文件可以重復(fù)利用杜顺,還可以減少帶寬财搁,降低網(wǎng)絡(luò)負(fù)荷。
所以躬络,緩存技術(shù)是無數(shù)WEB開發(fā)從業(yè)人員在工作過程中不可避免的一大問題尖奔。在產(chǎn)品開發(fā)的時(shí)候我們總是想辦法避免緩存產(chǎn)生,而在產(chǎn)品發(fā)布之時(shí)又在想策略管理緩存提升網(wǎng)頁的訪問速度穷当。了解瀏覽器的緩存命中原理提茁,是開發(fā)WEB應(yīng)用的基礎(chǔ)
2. 協(xié)商緩存與強(qiáng)緩存
2.1 強(qiáng)緩存:Expires&Cache-Control
當(dāng)瀏覽器對某個(gè)資源的請求命中了強(qiáng)緩存時(shí),返回的HTTP狀態(tài)為200馁菜,在chrome的開發(fā)者工具的network里面 size會顯示為from cache甘凭,比如:京東的首頁里就有很多靜態(tài)資源配置了強(qiáng)緩存,用chrome打開幾次火邓,再用f12查看network丹弱,可以看到有不少請求就是從緩存中加載的:
強(qiáng)緩存是利用Expires或者Cache-Control這兩個(gè)http response header實(shí)現(xiàn)的,它們都用來表示資源在客戶端緩存的有效期铲咨。
Expires是HTTP 1.0提出的一個(gè)表示資源過期時(shí)間的header躲胳,它描述的是一個(gè)絕對時(shí)間,由服務(wù)器返回纤勒,用GMT格式的字符串表示坯苹,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT,包含了Expires頭標(biāo)簽的文件摇天,就說明瀏覽器對于該文件緩存具有非常大的控制權(quán)粹湃。
例如,一個(gè)文件的Expires值是2020年的1月1日泉坐,那么就代表为鳄,在2020年1月1日之前,瀏覽器都可以直接使用該文件的本地緩存文件腕让,而不必去服務(wù)器再次請求該文件孤钦,哪怕服務(wù)器文件發(fā)生了變化。
所以,Expires是優(yōu)化中最理想的情況偏形,因?yàn)樗静粫a(chǎn)生請求静袖,所以后端也就無需考慮查詢快慢。它的緩存原理俊扭,如下:
-
瀏覽器第一次跟服務(wù)器請求一個(gè)資源队橙,服務(wù)器在返回這個(gè)資源的同時(shí),在response的header加上Expires的header萨惑,如:
瀏覽器在接收到這個(gè)資源后喘帚,會把這個(gè)資源連同所有response header一起緩存下來(所以緩存命中的請求返回的header并不是來自服務(wù)器,而是來自之前緩存的header)咒钟;
瀏覽器再請求這個(gè)資源時(shí)吹由,先從緩存中尋找,找到這個(gè)資源后朱嘴,拿出它的Expires跟當(dāng)前的請求時(shí)間比較倾鲫,如果請求時(shí)間在Expires指定的時(shí)間之前,就能命中緩存萍嬉,否則就不行乌昔;
如果緩存沒有命中,瀏覽器直接從服務(wù)器加載資源時(shí)壤追,Expires Header在重新加載的時(shí)候會被更新磕道;
Expires是較老的強(qiáng)緩存管理header,由于它是服務(wù)器返回的一個(gè)絕對時(shí)間行冰,在服務(wù)器時(shí)間與客戶端時(shí)間相差較大時(shí)溺蕉,緩存管理容易出現(xiàn)問題,比如:隨意修改下客戶端時(shí)間悼做,就能影響緩存命中的結(jié)果疯特。所以在HTTP 1.1的時(shí)候,提出了一個(gè)新的header肛走,就是Cache-Control漓雅,這是一個(gè)相對時(shí)間,在配置緩存的時(shí)候朽色,以秒為單位邻吞,用數(shù)值表示,如:Cache-Control:max-age=315360000葫男,它的緩存原理是:
-
瀏覽器第一次跟服務(wù)器請求一個(gè)資源抱冷,服務(wù)器在返回這個(gè)資源的同時(shí),在response的header加上Cache-Control的header腾誉,如:
瀏覽器在接收到這個(gè)資源后徘层,會把這個(gè)資源連同所有response header一起緩存下來;
瀏覽器再請求這個(gè)資源時(shí)利职,先從緩存中尋找趣效,找到這個(gè)資源后,根據(jù)它第一次的請求時(shí)間和Cache-Control設(shè)定的有效期猪贪,計(jì)算出一個(gè)資源過期時(shí)間跷敬,再拿這個(gè)過期時(shí)間跟當(dāng)前的請求時(shí)間比較,如果請求時(shí)間在過期時(shí)間之前热押,就能命中緩存西傀,否則就不行;
如果緩存沒有命中桶癣,瀏覽器直接從服務(wù)器加載資源時(shí)拥褂,Cache-Control Header在重新加載的時(shí)候會被更新;
Cache-Control描述的是一個(gè)相對時(shí)間牙寞,在進(jìn)行緩存命中的時(shí)候饺鹃,都是利用客戶端時(shí)間進(jìn)行判斷,所以相比較Expires间雀,Cache-Control的緩存管理更有效悔详,安全一些。
這兩個(gè)header可以只啟用一個(gè)惹挟,也可以同時(shí)啟用茄螃,當(dāng)response header中,Expires和Cache-Control同時(shí)存在時(shí)连锯,Cache-Control優(yōu)先級高于Expires:
此外归苍,還可以為 Cache-Control 指定 public
或 private
標(biāo)記。如果使用 private运怖,則表示該資源僅僅屬于發(fā)出請求的最終用戶霜医,這將禁止中間服務(wù)器(如代理服務(wù)器)緩存此類資源。對于包含用戶個(gè)人信息的文件(如一個(gè)包含用戶名的 HTML 文檔)驳规,可以設(shè)置 private肴敛,一方面由于這些緩存對其他用戶來說沒有任何意義,另一方面用戶可能不希望相關(guān)文件儲存在不受信任的服務(wù)器上吗购。需要指出的是医男,private 并不會使得緩存更加安全,它同樣會傳給中間服務(wù)器(如果網(wǎng)站對于傳輸?shù)陌踩砸蠛芨吣砻悖瑧?yīng)該使用傳輸層安全措施)镀梭。對于 public,則允許所有服務(wù)器緩存該資源踱启。通常情況下报账,對于所有人都可以訪問的資源(例如網(wǎng)站的 logo研底、圖片、腳本等)透罢,Cache-Control 默認(rèn)設(shè)為 public 是合理的榜晦。
2.2 協(xié)商緩存:Last-Modified&Etag
當(dāng)瀏覽器對某個(gè)資源的請求沒有命中強(qiáng)緩存,就會發(fā)一個(gè)請求到服務(wù)器羽圃,驗(yàn)證協(xié)商緩存是否命中乾胶,如果協(xié)商緩存命中,請求響應(yīng)返回的http狀態(tài)為304并且會顯示一個(gè)Not Modified的字符串朽寞,比如你打開京東的首頁识窿,按f12打開開發(fā)者工具,再按f5刷新頁面脑融,查看network喻频,可以看到有不少請求就是命中了協(xié)商緩存的:
查看單個(gè)請求的Response Header,也能看到304的狀態(tài)碼和Not Modified的字符串肘迎,只要看到這個(gè)就可說明這個(gè)資源是命中了協(xié)商緩存半抱,然后從客戶端緩存中加載的,而不是服務(wù)器最新的資源:
協(xié)商緩存是利用的是【Last-Modified膜宋,If-Modified-Since】和【ETag窿侈、If-None-Match】這兩對Header來管理的。
【Last-Modified秋茫,If-Modified-Since】的控制緩存的原理史简,如下:
-
瀏覽器第一次跟服務(wù)器請求一個(gè)資源,服務(wù)器在返回這個(gè)資源的同時(shí)肛著,在response的header加上Last-Modified的header圆兵,這個(gè)header表示這個(gè)資源在服務(wù)器上的最后修改時(shí)間:
-
瀏覽器再次跟服務(wù)器請求這個(gè)資源時(shí),在request的header上加上If-Modified-Since的header枢贿,這個(gè)header的值就是上一次請求時(shí)返回的Last-Modified的值:
-
服務(wù)器再次收到資源請求時(shí)殉农,根據(jù)瀏覽器傳過來If-Modified-Since和資源在服務(wù)器上的最后修改時(shí)間判斷資源是否有變化,如果沒有變化則返回304 Not Modified局荚,但是不會返回資源內(nèi)容超凳;如果有變化,就正常返回資源內(nèi)容耀态。當(dāng)服務(wù)器返回304 Not Modified的響應(yīng)時(shí)轮傍,response header中不會再添加Last-Modified的header,因?yàn)榧热毁Y源沒有變化首装,那么Last-Modified也就不會改變创夜,這是服務(wù)器返回304時(shí)的response header:
瀏覽器收到304的響應(yīng)后,就會從緩存中加載資源仙逻。
如果協(xié)商緩存沒有命中驰吓,瀏覽器直接從服務(wù)器加載資源時(shí)涧尿,Last-Modified Header在重新加載的時(shí)候會被更新,下次請求時(shí)檬贰,If-Modified-Since會啟用上次返回的Last-Modified值姑廉。
【Last-Modified,If-Modified-Since】都是根據(jù)服務(wù)器時(shí)間返回的header偎蘸,一般來說庄蹋,在沒有調(diào)整服務(wù)器時(shí)間和篡改客戶端緩存的情況下瞬内,這兩個(gè)header配合起來管理協(xié)商緩存是非趁匝可靠的,但是有時(shí)候也會服務(wù)器上資源其實(shí)有變化虫蝶,但是最后修改時(shí)間卻沒有變化的情況章咧,而這種問題又很不容易被定位出來,而當(dāng)這種情況出現(xiàn)的時(shí)候能真,就會影響協(xié)商緩存的可靠性赁严。所以就有了另外一對header來管理協(xié)商緩存,這對header就是【ETag粉铐、If-None-Match】疼约。它們的緩存管理的方式是:
-
瀏覽器第一次跟服務(wù)器請求一個(gè)資源,服務(wù)器在返回這個(gè)資源的同時(shí)蝙泼,在response的header加上ETag的header程剥,這個(gè)header是服務(wù)器根據(jù)當(dāng)前請求的資源生成的一個(gè)唯一標(biāo)識,這個(gè)唯一標(biāo)識是一個(gè)字符串汤踏,只要資源有變化這個(gè)串就不同织鲸,跟最后修改時(shí)間沒有關(guān)系,所以能很好的補(bǔ)充Last-Modified的問題:
-
瀏覽器再次跟服務(wù)器請求這個(gè)資源時(shí)溪胶,在request的header上加上If-None-Match的header搂擦,這個(gè)header的值就是上一次請求時(shí)返回的ETag的值:
-
服務(wù)器再次收到資源請求時(shí),根據(jù)瀏覽器傳過來If-None-Match和然后再根據(jù)資源生成一個(gè)新的ETag哗脖,如果這兩個(gè)值相同就說明資源沒有變化瀑踢,否則就是有變化;如果沒有變化則返回304 Not Modified才避,但是不會返回資源內(nèi)容丘损;如果有變化,就正常返回資源內(nèi)容工扎。與Last-Modified不一樣的是徘钥,當(dāng)服務(wù)器返回304 Not Modified的響應(yīng)時(shí),由于ETag重新生成過肢娘,response header中還會把這個(gè)ETag返回呈础,即使這個(gè)ETag跟之前的沒有變化:
瀏覽器收到304的響應(yīng)后舆驶,就會從緩存中加載資源。
Etag和Last-Modified非常相似而钞,都是用來判斷一個(gè)參數(shù)沙廉,從而決定是否啟用緩存。但是ETag相對于Last-Modified也有其優(yōu)勢臼节,可以更加準(zhǔn)確的判斷文件內(nèi)容是否被修改撬陵,從而在實(shí)際操作中實(shí)用程度也更高。
協(xié)商緩存跟強(qiáng)緩存不一樣网缝,強(qiáng)緩存不發(fā)請求到服務(wù)器巨税,所以有時(shí)候資源更新了瀏覽器還不知道,但是協(xié)商緩存會發(fā)請求到服務(wù)器粉臊,所以資源是否更新草添,服務(wù)器肯定知道。大部分web服務(wù)器都默認(rèn)開啟協(xié)商緩存扼仲,而且是同時(shí)啟用【Last-Modified远寸,If-Modified-Since】和【ETag、If-None-Match】屠凶,比如apache:
如果沒有協(xié)商緩存驰后,每個(gè)到服務(wù)器的請求,就都得返回資源內(nèi)容矗愧,這樣服務(wù)器的性能會極差灶芝。
【Last-Modified,If-Modified-Since】和【ETag贱枣、If-None-Match】一般都是同時(shí)啟用监署,這是為了處理Last-Modified不可靠的情況。有一種場景需要注意:
分布式系統(tǒng)里多臺機(jī)器間文件的Last-Modified必須保持一致纽哥,以免負(fù)載均衡到不同機(jī)器導(dǎo)致比對失敗钠乏;
分布式系統(tǒng)盡量關(guān)閉掉ETag(每臺機(jī)器生成的ETag都會不一樣);
比如春塌,京東頁面的資源請求晓避,返回的repsonse header就只有Last-Modified,沒有ETag:
協(xié)商緩存需要配合強(qiáng)緩存使用只壳,上面這個(gè)截圖中俏拱,除了Last-Modified這個(gè)header,還有強(qiáng)緩存的相關(guān)header吼句,因?yàn)槿绻粏⒂脧?qiáng)緩存的話锅必,協(xié)商緩存根本沒有意義。
3. 緩存判斷流程
如果資源已經(jīng)被瀏覽器緩存下來,在緩存失效之前搞隐,再次請求時(shí)驹愚,默認(rèn)會先檢查是否命中強(qiáng)緩存,如果強(qiáng)緩存命中則直接讀取緩存劣纲,如果強(qiáng)緩存沒有命中則發(fā)請求到服務(wù)器檢查是否命中協(xié)商緩存逢捺,如果協(xié)商緩存命中,則告訴瀏覽器還是可以從緩存讀取癞季,否則才從服務(wù)器返回最新的資源劫瞳。其瀏覽器判斷緩存的詳細(xì)流程圖,如下:
4. 緩存優(yōu)點(diǎn)
(1)減少頁面加載時(shí)間绷柒;
(2)減少服務(wù)器負(fù)載志于;
-
通用首部字段(就是請求報(bào)文和響應(yīng)報(bào)文都能用上的字段)
-
請求首部字段
-
響應(yīng)首部字段
-
實(shí)體首部字段
5. 瀏覽器緩存機(jī)制
1)瀏覽器在加載資源時(shí),先根據(jù)這個(gè)資源的一些http header判斷它是否命中強(qiáng)緩存辉巡,強(qiáng)緩存如果命中恨憎,瀏覽器直接從自己的緩存中讀取資源蕊退,不會發(fā)請求到服務(wù)器郊楣。比如:某個(gè)css文件,如果瀏覽器在加載它所在的網(wǎng)頁時(shí)瓤荔,這個(gè)css文件的緩存配置命中了強(qiáng)緩存净蚤,瀏覽器就直接從緩存中加載這個(gè)css,連請求都不會發(fā)送到網(wǎng)頁所在服務(wù)器输硝;
2)當(dāng)強(qiáng)緩存沒有命中的時(shí)候今瀑,瀏覽器一定會發(fā)送一個(gè)請求到服務(wù)器,通過服務(wù)器端依據(jù)資源的另外一些http header驗(yàn)證這個(gè)資源是否命中協(xié)商緩存点把,如果協(xié)商緩存命中橘荠,服務(wù)器會將這個(gè)請求返回,但是不會返回這個(gè)資源的數(shù)據(jù)郎逃,而是告訴客戶端可以直接從緩存中加載這個(gè)資源哥童,于是瀏覽器就又會從自己的緩存中去加載這個(gè)資源;
3)強(qiáng)緩存與協(xié)商緩存的共同點(diǎn)是:如果命中褒翰,都是從客戶端緩存中加載資源贮懈,而不是從服務(wù)器加載資源數(shù)據(jù);區(qū)別是:強(qiáng)緩存不發(fā)請求到服務(wù)器优训,協(xié)商緩存會發(fā)請求到服務(wù)器朵你。
4)當(dāng)協(xié)商緩存也沒有命中的時(shí)候,瀏覽器直接從服務(wù)器加載資源數(shù)據(jù)揣非。
二抡医、跨域
1. 概述
幾個(gè)疑問:
- 什么是跨域?如何判斷是否產(chǎn)生「跨域」早敬?
- 跨域忌傻,帶來的問題毛仪?
- 跨域問題,解決思路芯勘?
2. 跨域:是什么
跨域的問題根源:瀏覽器的「同源策略」箱靴。
2.1. 同源策略
同源策略(Same-Origin Policy):1995 年,Netscape 公司荷愕,將「同源策略」引入瀏覽器衡怀,此后,所有瀏覽器都遵循「同源策略」安疗。
同源策略:
- A 網(wǎng)頁設(shè)置的 Cookie抛杨,B 網(wǎng)頁不能訪問,除非「A B 同源」荐类。
- 即:非同源網(wǎng)站之間怖现, Cookie 隔離。
同源:是指「3 個(gè)相同」
- 協(xié)議
- 域名
- 端口
同源策略的意義:瀏覽器安全的基石玉罐,保證用戶信息安全屈嗤,防止惡意網(wǎng)站竊取數(shù)據(jù)。
Cookie 是存儲在瀏覽器端的文本信息吊输,通常存儲一些個(gè)人隱私信息饶号,大部分網(wǎng)站通過 Cookie 內(nèi)信息識別用戶的登陸狀態(tài),如果 Cookie 被惡意竊取季蚂,則產(chǎn)生巨大安全隱患茫船。
思考:
同源的網(wǎng)站,會共享 Cookie扭屁,還會共享其他信息么算谈?
隨著互聯(lián)網(wǎng)的發(fā)展,更加嚴(yán)格的「同源策略」:如果 A網(wǎng)站和 B網(wǎng)站料滥,不同源然眼,則
- Cookie、LocalStorage 和 IndexDB 無法讀取
- DOM 無法獲得
- AJAX 請求不能發(fā)送
「同源策略」絕大部分情況下幔欧,都很必要罪治,但也限制了業(yè)務(wù)的靈活應(yīng)用,一些特殊場景下礁蔗,期望繞過「同源策略」觉义。
2.2. 跨域
核心幾點(diǎn):
- 跨域:發(fā)生在瀏覽器
- 跨域的根源:瀏覽器為了安全所遵循的「同源策略」
- 同一個(gè)域:3 個(gè)相同
3. 跨域:帶來的問題
跨域時(shí),2 個(gè)請求無法共享 Cookie 等數(shù)據(jù)浴井,也無法嵌套發(fā)送 Ajax 請求晒骇。
解決辦法:
- 請求無法共享 Cookie 數(shù)據(jù):網(wǎng)頁設(shè)置 document.domain 參數(shù),實(shí)現(xiàn)一級域名共享 Cookie
- 無法嵌套發(fā)送 Ajax 請求:需要特殊處理。(見下文)
跨域:解決方案
3.1. Cookie
Cookie 是服務(wù)器寫入瀏覽器的一小段信息洪囤,只有同源的網(wǎng)頁才能共享徒坡。但是,兩個(gè)網(wǎng)頁一級域名相同瘤缩,只是二級域名不同喇完,瀏覽器允許通過設(shè)置document.domain共享 Cookie。
舉例來說剥啤,A網(wǎng)頁是http://w1.example.com/a.html锦溪,B網(wǎng)頁是http://w2.example.com/b.html,那么只要設(shè)置相同的document.domain府怯,兩個(gè)網(wǎng)頁就可以共享Cookie刻诊。
document.domain = 'example.com';
現(xiàn)在,A網(wǎng)頁通過腳本設(shè)置一個(gè) Cookie牺丙。
document.cookie = "test1=hello";
B網(wǎng)頁就可以讀到這個(gè) Cookie涕烧。
var allCookie = document.cookie;
注意癌佩,這種方法只適用于 Cookie 和 iframe 窗口映之,LocalStorage 和 IndexDB 無法通過這種方法擦剑,規(guī)避同源政策,而要使用PostMessage API民假。另外浮入,服務(wù)器也可以在設(shè)置Cookie的時(shí)候龙优,指定Cookie的所屬域名為一級域名羊异,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
這樣的話彤断,二級域名和三級域名不用做任何設(shè)置野舶,都可以讀取這個(gè)Cookie。
3.2. Ajax
同源政策規(guī)定宰衙,AJAX請求只能發(fā)給同源的網(wǎng)址平道,否則就報(bào)錯(cuò)。
除了架設(shè)服務(wù)器代理(瀏覽器請求同源服務(wù)器供炼,再由后者請求外部服務(wù))一屋,有三種方法規(guī)避這個(gè)限制。
- JSONP
- WebSocket
- CORS
3.2.1. JSONP
完整內(nèi)容袋哼,參考:same-origin-policy
本質(zhì):
- 網(wǎng)頁內(nèi)部冀墨,通過
- 服務(wù)端受到請求后,需要將 data 放入指定名字「回調(diào)函數(shù)」中傳回涛贯,避免使用 JSON.parse 步驟诽嘉。
特點(diǎn):
- 需要服務(wù)器配套改造。
3.2.2. CORS
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標(biāo)準(zhǔn)虫腋,是跨源AJAX請求的根本解決方法骄酗。相比JSONP只能發(fā)GET請求,CORS允許任何類型的請求悦冀。
關(guān)鍵點(diǎn):
- CORS需要瀏覽器和服務(wù)器同時(shí)支持趋翻。
- 目前,所有瀏覽器都支持該功能盒蟆,IE瀏覽器不能低于IE10嘿歌。
- 整個(gè)CORS通信過程,都是瀏覽器自動完成茁影,不需要用戶參與宙帝。
- 對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別募闲,代碼完全一樣步脓。
- 瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息浩螺,有時(shí)還會多出一次附加的請求靴患,但用戶不會有感覺。
因此要出,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器鸳君。只要服務(wù)器實(shí)現(xiàn)了CORS接口,就可以跨源通信患蹂。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)或颊。
只要同時(shí)滿足以下兩大條件,就屬于簡單請求传于。
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值application/x-www-form-urlencoded囱挑、multipart/form-data、text/plain
凡是不同時(shí)滿足上面兩個(gè)條件沼溜,就屬于非簡單請求平挑。 瀏覽器對這兩種請求的處理,是不一樣的系草。 詳細(xì)內(nèi)容通熄,參考:《跨域資源共享 CORS》