一、輸入url到顯示頁面發生了什麼
- 瀏覽器的地址欄輸入URL並按下回車;
- 瀏覽器查找當前URL是否存在緩存,並比較緩存是否過時;
- DNS解析URL對應的IP。先在本地hosts文件查找,若是沒找到,再到DNS服務器(由中國電信,中國移動等服務商提供)查找;
- 根據IP創建TCP鏈接(三次握手);
- 發送 HTTP 請求報文,包括(1)請求方法URI協議/版本(2)請求頭(Request Header)(3)請求正文;
- 服務器處理請求,返回響應報文,瀏覽器接收HTTP響應,包含(1)狀態行(2)響應頭(Response Header)(3)響應正文;
- 若是服務器發現這個url須要重定向,則會返回重定向的響應,這是爲了seo,
301
永久重定向,302
臨時重定向,若是有重定向,瀏覽器會從新請求重定向以後的地址;
- 關閉TCP鏈接(四次揮手);
- 瀏覽器解析html並渲染到頁面;
ip和域名的對應關係:javascript
- 任意多個域名能夠解析到同一個IP,服務器根據header判斷請求的是哪個域名。
- 一個域名也能夠對應多個IP,DNS服務商根據你的位置和運營商返回不一樣的解析結果。
二、html解析
解析順序
- 先把html下載下來;
- 分析文檔結構,具體的是一個樹型結構,是各個節點的層級關係,若是文檔中有資源不符合安全策略(若有的頁面不容許加載跨域的資源),則會給出警告。並對全部的資源進行優先級排序;
- 開啓下載線程,按照排好的優先級進行資源下載,通常css和font這類文件會優先加載。瀏覽器對同一域名下的下載併發不超過 6 個,不一樣域名的話,在瀏覽器設置的最大併發上限之內(默認是10個);
- 同時開啓文檔結構解析的線程。(4和3是同時進行的,算兩個線程)自上而下構建dom,生成DOM Tree,按照 深度優先 的原則,將一個節點的所有子節點生成完成以後纔會開始生成當前節點的兄弟節點;
- 瀏覽器會經過DOM Tree 和 CSS Rule Tree 來構造 Rendering Tree。根據 Rendering Tree計算每一個元素的位置、大小,這個過程叫reflow(迴流)。當元素的位置、大小肯定以後再去計算每一個元素的字體大小、顏色等,這個過程叫repaint(重繪)。
阻塞型資源:
- 內聯css
- 內聯javascript
- 外聯普通javascript
- 外聯defer javascript
- javascript標籤以前的外聯css
非阻塞型資源:
- javascript標籤以後的外聯css
- image
- iframe
外聯async javascript若是下載完後,dom還沒解析完畢,會直接執行,阻塞。若是下載完後dom已經解析完畢,則不算阻塞。下載不會阻塞,運行可能會阻塞php
解析dom的規則
- 遇到 DOM 標籤時,執行 DOM 構建,將該 DOM 元素添加到
dom tree
中。
- 遇到內聯的css,會生成cssom,和前邊的
cssom
合併成css rule tree
,瀏覽器解析CSS選擇器是按照從右往左的順序解析的
- 遇到內聯的javascript會直接執行阻塞後續內容的解析,直接執行js;
- 遇到
link
標籤時,不會阻塞後續內容的解析(好比 DOM 構建),檢查 link 資源是否已下載,若是已下載,則構建 cssom,前邊的cssom合併成css rule tree,未下載則開啓線程下載。
- 遇到
script
標籤時,首先阻塞後續內容的解析,同時檢查該script是否已經下載下來,若是已下載,便執行代碼.若是還未下載完畢,則開啓線程下載,如今完成後當即執行。
- 遇到
script defer
標籤時,先檢查是否已經下載,若是已經下載,則繼續往下走構建dom,若是未下載,則開啓新線程下載,下載完成後,若是dom樹還未構造完畢,則等待dom樹構建完畢後執行。如故dom樹已經構建完畢,則當即執行。在派發DOMContentLoaded
事件以前
- 遇到
script async
標籤時, 先檢查是否已經下載,若是已經下載,則阻塞後續內容解析,直接執行。若是還未下載,則開啓線程下載,下載完成後當即執行。可能在dom樹構建完成(DOMContentLoaded)以前執行,也可能在以後執行。但必定在onload
以前
- 遇到
<img>
或者<video>
標籤,不會阻塞頁面,若是沒有下載完畢,則進入下載線程的隊列。img和video不影響DOMContentLoaded
事件的派發。
- html中每遇到< script >標籤,頁面就會從新渲染(瀏覽器會將dom tree和css rule tree合併成render tree)一次,由於要保證標籤中的JS代碼拿到的都是最新的樣式。
DOMContentLoaded和onload
DOMContentLoaded爲頁面dom構建完成,同步js的代碼執行完畢。圖片資源,js裏引入的異步資源等不必定加載完畢了; onload在DOMContentLoaded以後,圖片資源,異步的js資源都下載完畢纔會觸發。css
三、前端緩存
- 後端緩存(數據庫緩存,CDN緩存,代理服務器緩存等)
- 前端緩存,按照優先級
Service Worker
、Memory Cache
、Disk Cache
(也叫http chache
,分爲強緩存
和協商緩存
)、Push Cache
;
Service Worker
- 是運行在瀏覽器背後的獨立線程。主要用來實現離線緩存、消息推送和網絡代理等功能,
PWA
(Progressive Web App漸進式的web App)應用較多。
memory cache
- 內存中的緩存,瀏覽器的 TAB 關閉後該次瀏覽的 memory cache 便告失效;
- 兩種資源:(1)瀏覽器preloader發現的資源,好比css.html.font這些會優先下載;(2)顯式指定的預加載資源
<link rel="preload">
,也會被放入 memory cache 中
- memory cache 機制保證了一個頁面中若是有兩個相同的請求 (例如兩個 src 相同的
<img>
,兩個 href 相同的 <link>
) 都實際只會被請求最多一次,避免浪費。不過在匹配緩存時,除了匹配徹底相同的 URL 以外,還會比對他們的類型,CORS 中的域名規則等。所以一個做爲腳本 (script) 類型被緩存的資源是不能用在圖片 (image) 類型的請求中的,即使他們 src 相等。
- 使用
no-store
即使是 memory cache 也不會存儲
Disk Cache(http chache)緩存策略
強緩存
Cache-Control
的幾個取值含義:
- private: 僅瀏覽器能夠緩存
- public: 瀏覽器和代理服務器均可以緩存,用的多一些
- max-age=xxx 過時時間(重要)
- no-cache 不進行強緩存(重要)
- no-store 不強緩存,也不協商緩存,真正意義上的不緩
判斷該資源是否命中強緩存,就看 response 中 Cache-Control 的值,若是有max-age=xxx
秒,則命中強緩存。若是Cache-Control的值是no-cache
,或者max-age=0
(強緩存,但已通過期了)說明沒命中強緩存,走協商緩存。html
Cache-Control優先級高於Expires(http舊的版本會用到) Expires 指緩存過時的時間,會有必定弊端,好比用戶修改了本地時間 簡單粗暴,若是資源沒過時,就取緩存,若是過時了,則請求服務器,看是走協商緩存(服務端返回304),仍是請求新的數據前端
協商緩存
- response header中的
ETag
和Last-Modified
,request header中對應If-None-Match
和If-Modified-Since
- ETag優先級高於Last-Modified,會先判斷ETag,再判斷Last-Modified
ETag是爲了解決幾個Last-Modified比較難解決的問題:
- 一些文件也許會週期性的更改,可是他的內容並不改變(僅僅改變的修改時間),這個時候咱們並不但願客戶端認爲這個文件被修改了,而從新GET;
- 某些文件修改很是頻繁,好比在秒如下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改沒法判斷(或者說UNIX記錄MTIME只能精確到秒);
- 某些服務器不能精確的獲得文件的最後修改時間。
一些操做觸發的緩存策略
- 地址欄回車,頁面連接跳轉,新開窗口,前進後退這些操做會走強緩存
- F5 會 跳過強緩存規則,直接走協商緩存;
- Ctrl+F5 ,跳過全部緩存規則,和第一次請求同樣,從新獲取資源
vue項目較爲合理的緩存方案
- HTML:使用協商緩存。
- CSS&JS&圖片:使用強緩存,文件命名帶上hash值。
Push Cache
- Push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它纔會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,而且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右。
四、瀏覽器存儲
Cookie
發送的cookie格式:
Cookie: name1=value1 [; name2=value2]
vue
name
:一個惟一肯定的cookie名稱。一般來說cookie的名稱是不區分大小寫的。
value
:存儲在cookie中的字符串值。最好爲cookie的name和value進行url編碼
domain
:cookie對於哪一個域名下是有效的。全部向該域發送的請求中都會包含這個cookie信息。這個值能夠包含子域(如:m.baidu.com),也能夠不包含它(如:.baidu.com,則對於baidu.com的全部子域都有效).
path
: 表示這個cookie影響到的路徑,瀏覽器跟會根據這項配置,向指定域中匹配的路徑發送cookie。
expires
:過時時間,表示cookie自刪除的時間戳。若是不設置這個時間戳,cookie就會變成會話Session類型的cookie,瀏覽器會在頁面關閉時即將刪除全部cookie,這個值是GMT時間格式,若是客戶端和服務器端時間不一致,使用expires就會存在誤差。
max-age
: 與expires做用相同,用來告訴瀏覽器此cookie多久過時(單位是秒),而不是一個固定的時間點。正常狀況下,max-age的優先級高於expires。
HttpOnly
: 告知瀏覽器不容許經過腳本document.cookie去更改這個值,一樣這個值在document.cookie中也不可見。但在http請求仍然會攜帶這個cookie。注意這個值雖然在腳本中不可獲取,但仍然在瀏覽器安裝目錄中以文件形式存在。這項設置一般在服務器端設置。
secure
: 安全標誌,指定後只有在使用SSL(https)連接時候纔會發送到服務器,若是是http連接則不會傳遞該值。可是也有其餘方法能在本地查看到cookie
Cookie的優勢
- cookie鍵值對形式,結構簡單
- 能夠配置過時時間,不須要任何服務器資源存在於客戶端上,
- 能夠彌補HTTP協議無狀態的部分不足
- 無兼容性問題。
Cookie的缺點
- 大小數量受到限制,每一個domain最多隻能有20條cookie,每一個cookie長度不能超過4096或8192 字節,不然會被截掉。
- 用戶配置可能爲禁用 有些用戶禁用了瀏覽器或客戶端設備接收 Cookie 的能力,所以限制了這一功能。
- 增長流量消耗,每次請求都須要帶上cookie信息。
- 安全風險,黑客能夠進行Cookie攔截、XSS跨站腳本攻擊和Cookie欺騙,歷史上由於Cookie被攻擊的網站用戶不在少數,雖然能夠對Cookie進行加密解密,但會影響到性能。
localStorage/sessionStorage
- 存儲數據量大,5MB。
- 不會隨http請求一塊兒發送,有效的減小了請求大小
- sessionStorage只做用於當前窗口,不能跨窗口讀取其餘窗口的SessionStorage數據庫信息,瀏覽器每次新建、關閉都是直接致使當前窗口的數據庫新建和銷燬。
- localStorage除非手動清理掉,不然會一直存在,不論瀏覽器,頁面標籤是否關閉。
indexedDB
- 瀏覽器內置的數據庫,和
NoSQL
很像,與service work
搭配,實現離線訪問;
- 數據儲存量無限大(只要你硬盤夠),Chrome規定了最多隻佔硬盤可用空間的1/3;
- 操做大量數據的時候,可能存在性能上的消耗。
- 兼容性問題,只有ie11以上支持。
五、http版本
HTTP/1.1
- 線頭阻塞 (Head Of Line Blocking) 問題
- TCP 鏈接數限制.對於同一個域名,瀏覽器最多隻能同時建立 6~8 個 TCP 鏈接 (不一樣瀏覽器不同)。爲了解決數量限制,出現了 域名分片 技術,其實就是資源分域,將資源放在不一樣域名
HTTP/2
- HTTP/2解決了HTTP的隊頭阻塞問題,可是並無解決TCP隊頭阻塞問題
- 二進制分幀,多路複用
- 服務端推送
HTTP/3
- Google開發QUIC協議,基於UDP實現,減小了 RTT
- 解決了隊頭阻塞問題
HTTP狀態碼
- 1xx:表示目前是協議的中間狀態,還須要後續請求
- 3xx:301永久重定向,302臨時重定向。304協商緩存
- 4xx:404資源未找到
六、請求實現的方式
XMLHttpRequest
- 低版本IE的實現爲ActiveXObject
- JQuery的$ajax是對XHR的封裝,是針對mvc的編程模式,不太適合目前mvvm的編程模式。
- axios也是對原生XHR的一種封裝,不過是Promise實現版本
fetch
- 寫法簡單,是Promise的實現
- 沒法取消請求,使用setTimeout及Promise.reject的實現的超時控制並不能阻止請求過程繼續在後臺運行。
- fetch不支持同步請求
- fetch默認不會帶cookie,須要添加配置項
- fetch沒有辦法原生監測請求的進度,而XHR能夠
navigator.sendBeacon
- 只能發送post請求;
- 頁面關閉,仍能夠繼續發送完;
- 通常用於window的unload事件裏,但不只限於此;
- 更多請參考高程
window.addEventListener('unload', function(){
navigator.sendBeacon('/post1', '{foo: "bar"}')
})
複製代碼
WebSocket
由於HTTP 協議作不到服務器主動向客戶端推送信息。WebSocket能夠作到雙向會話。java
var s = new WebSocket('ws://www.a.com/s.php')
// 必須傳入絕對URL,能夠是任何網站
- 鏈接成功後,會有101狀態碼,表示切換請求協議,從HTTP切換到WebSocket
6.一、三種Content-Type
application/x-www-form-urlencoded
普通的表單提交,將鍵值對的參數用&鏈接起來,若是有空格,將空格轉換爲+加號;有特殊符號,將特殊符號轉換爲ASCII HEX值node
JQuery的ajax默認值是這個ios
傳入一個json對象後,通常須要通過qs.stringify()處理,真實的傳入是web
key1=val1&key2=val2&key3=val3
複製代碼
multipart/form-data
不會對參數編碼,使用的boundary(分割線)。上傳文件的操做,用到的是這種 上傳一個FormDate對象時,content-type是這個值,真實的可能長這樣
multipart/form-data; boundary=----WebKitFormBoundaryAqr2Zs4BNE8Z8FWR
複製代碼
application/json
上傳json格式的數據,適合層級較深的數據,會進行JSON.stringify。
咱們日常在使用axios的時候,有時會用到qs.stringify()這個方法。注意區別
var a = {name:'hehe',age:10};
qs.stringify(a)
JSON.stringify(a)
複製代碼
由於get請求須要把參數拼接在連接後面,因此能夠用qs.stringify()簡單處理
XSS和CSRF
XSS
Cross-Site Scripting,跨站腳本攻擊,解決方法:
- cookie設置httpOnly
- url,搜索參數等進行轉義
- 輸入內容,危險字符過濾,長度限制
CSRF
Cross-site request forgery跨站請求僞造
八、跨域
同源策略
協議,域名(子域名和主域名),端口三個都相同被認爲是同源。若是當前網址(http://news.a.com
),如下都不是同源
https://news.a.com
不一樣協議
http://news.a.com:8080
不一樣端口
http://home.a.com
不一樣域名
同源策略的約束
- Cookie、LocalStorage 和 IndexDB 沒法讀取。
- JavaScript 的 API 中的一些引用,沒法得到。如獲取iframe裏的dom節點,進行操做是不被容許的
- AJAX 請求不能發送。(也就是沒法使用XMLHttpRequest)
解決跨域的方法
跨域資源共享(CORS)
- 在發送請求時會有個Origin頭表示請求頁面的源信息, 若是服務器返回的Access-Control-Allow-Origin中有相同的源信息或是* 那麼就能夠跨域請求信息
- 請求和響應都不包含cookie
options預檢請求
在跨域的狀況下,在瀏覽器發起"複雜請求"時會主動發起options預檢請求,獲知服務端是否容許該跨域請求。服務器確認容許以後,才發起實際的 HTTP 請求。
簡單請求與複雜請求
簡單請求不會觸發options預檢,只有複雜請求會
簡單請求
- 請求方法爲GET、HEAD、POST時發的請求
- 人爲設置了規範集合以內的首部字段,如Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width
- Content-Type 的值僅限於下列三者之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain
- 請求中的任意 XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器;
- 請求中沒有使用 ReadableStream 對象。
複雜請求
- 使用了下面任一 HTTP 方法,PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
- 人爲設置瞭如下集合以外首部字段,即簡單請求外的字段
- Content-Type 的值不屬於下列之一,即application/x-www-form-urlencoded、multipart/form-data、text/plain
參考 面試官:說說你對 options 請求的理解
圖片探測
let img = new Image();
img.onload = img.onerror = function() {
console.log('done');
};
img.src = 'http://****/test?name=abc';
複製代碼
圖片探測只能發送get請求,沒法獲取服務器響應的內容。用圖片探測只能與服務器單向通訊。
jsonp
- jsonp方法主要是動態建立script標籤來得到數據;
- jsonp只能進行get請求,由於script中的src屬於靜態文件。
- 利用了js文件下載後會直接執行,相似eval()執行的特色。在src後面拼接上?callback=fn傳入回調函數方法名,後端返回函數的調用。
document.domain
瀏覽器容許經過設置document.domain共享 Cookie,來達成效果。可是,兩個網頁一級域名相同,只是二級域名不一樣才能夠設置。
A網頁:http://w1.cs.com/a.html
在這個網頁地址中,w1.cs.com這部分統稱爲域名
- 一級域名是由一個合法的字符串+域名後綴組成,因此,cs.com這種形式的域名纔是一級域名,cs是域名主體,.com、.net也是域名後綴。
- 二級域名實際就是一級域名下面的主機名,顧名思義,它是在一級域名前面加上一個字符串,好比w1.cs.com,w2.cs.com
網頁中設置document.domain和cookie能夠這樣
document.domain = 'cs.com';
document.cookie = "key1=value1";
複製代碼
服務器也能夠在設置Cookie的時候,指定Cookie的所屬域名爲一級域名,好比.example.com
Set-Cookie: key=value; domain=.example.com; path=/
複製代碼
window.postMessage
九、js執行順序,宏任務和微任務
一個進程的運行,固然須要不少個線程互相配合, JS是單線程的,onclick回調、setTimeout、Ajax這些是由於瀏覽器或node(宿主環境)是多線程的,即瀏覽器搞了幾個其餘線程去輔助JS線程的運行。
瀏覽器線程
- GUI 渲染線程(DOM的渲染)
- JS 引擎線程
- 定時器觸發線程 (setTimeout)
- 瀏覽器事件線程 (onclick)
- http 異步線程
- 輪詢處理線程event loop
event loop執行順序
- 一開始整個腳本做爲一個宏任務執行;
- 執行過程當中同步代碼直接執行,宏任務進入宏任務隊列,微任務進入微任務隊列;
- 當前宏任務執行完出隊,檢查微任務列表,有則依次執行,直到所有執行完;
- 執行瀏覽器UI線程的渲染工做;
- 檢查是否有Web Worker任務,有則執行;
- 執行完本輪的宏任務,回到2,依此循環,直到宏任務和微任務隊列都爲空;
tips:
- 宏任務必然是在微任務以後才執行的(由於微任務其實是宏任務的其中一個步驟)
- DOM Tree的修改是實時的,而修改的Render到DOM上纔是異步的
- new Promise執行自己時是屬於同步代碼,只有.then纔是微任務
- async/await本質上仍是基於Promise的一些封裝,而Promise是屬於微任務的一種。因此在使用await關鍵字與Promise.then效果相似
微任務
Promise.then()
和catch()
;
- Promise爲基礎開發的其它技術,好比fetch API;
- Node獨有的
process.nextTick
;
- V8的垃圾回收過程;
MutationObserver
;
宏任務
setTimeout
、setInterval
、setImmediate
(node環境)、
- script、
- UI rendering、
- I/O、
- 網絡請求等等
requestAnimationFrame
既不屬於宏任務,也不屬於微任務
十、垃圾回收和內存泄漏
垃圾回收:
JavaScript代碼運行時,須要分配內存空間來儲存變量和值。當變量不在參與運行時,就須要系統收回被佔用的內存空間,這就是垃圾回收。
回收機制
- Javascript 具備自動垃圾回收機制,會按期對那些再也不使用的變量、對象所佔用的內存進行釋放,原理就是找到再也不使用的變量,而後釋放掉其佔用的內存。
- JavaScript中存在兩種變量:局部變量和全局變量。全局變量的生命週期會持續要頁面卸載;而局部變量聲明在函數中,它的生命週期從函數執行開始,直到函數執行結束,在這個過程當中,局部變量會在堆或棧中存儲它們的值,當函數執行結束後,這些局部變量再也不被使用,它們所佔有的空間就會被釋放。
- 不過,當局部變量被外部函數使用時,其中一種狀況就是閉包,在函數執行結束後,函數外部的變量依然指向函數內部的局部變量,此時局部變量依然在被使用,因此不會回收。
垃圾回收的方式
致使內存泄漏的狀況
- 意外的全局變量: 因爲使用未聲明的變量,而意外的建立了一個全局變量,而使這個變量一直留在內存中沒法被回收。
- 被遺忘的計時器或回調函數: 設置了 setInterval 定時器,而忘記取消它,若是循環函數有對外部變量的引用的話,那麼這個變量會被一直留在內存中,而沒法被回收。
- 脫離 DOM 的引用: 獲取一個 DOM 元素的引用,然後面這個元素被刪除,因爲一直保留了對這個元素的引用,因此它也沒法被回收。
- 閉包: 不合理的使用閉包,從而致使某些變量一直被留在內存當中。
減小垃圾回收
雖然瀏覽器能夠進行垃圾自動回收,可是當代碼比較複雜時,垃圾回收所帶來的代價比較大,因此應該儘可能減小垃圾回收。
- 對數組進行優化: 在清空一個數組時,最簡單的方法就是給其賦值爲[ ],可是與此同時會建立一個新的空對象,能夠將數組的長度設置爲0,以此來達到清空數組的目的。
- 對object進行優化: 對象儘可能複用,對於再也不使用的對象,就將其設置爲null,儘快被回收。
- 對函數進行優化: 在循環中的函數表達式,若是能夠複用,儘可能放在函數的外面。