本小節主要記錄了網頁中一些其餘的知識點:javascript
瀏覽器的核心是兩部分:渲染引擎和 JavaScript引擎。html
渲染引擎的主要做用是,將網頁代碼渲染爲用戶視覺能夠感知的平面文檔。渲染引擎處理網頁,一般分紅四個階段:java
- 解析代碼:HTML代碼解析爲 DOM,CSS代碼解析爲 CSSOM(CSS Object Model)。
- 對象合成:將 DOM和 CSSOM合成一棵渲染樹。
- 佈局:計算出渲染樹的佈局(layout)。
- 繪製:將渲染樹繪製到屏幕。
以上四步並不是嚴格按順序執行,每每第一步還沒完成,第二步和第三步就已經開始了。因此會看到這種狀況:網頁的HTML代碼還沒下載完,但瀏覽器已經顯示出內容。web
來看一下很是重要的重流和重繪。渲染樹轉換爲網頁佈局,稱爲「佈局流」(flow);佈局顯示到頁面的這個過程,稱爲「繪製」(paint)。它們都具備阻塞效應,而且會耗費不少時間和計算資源。json
頁面生成之後,腳本操做和樣式表操做都會觸發「重流」(reflow)和「重繪」(repaint)。用戶的互動也會觸發重流和重繪,如改變窗口大小、頁面滾動、設置鼠標懸停
a:hover
效果、在輸入框中輸入文本等。api重流和重繪並不必定一塊兒發生,重流必然致使重繪,重繪不必定須要重流。好比改變元素顏色,只會致使重繪,而不會致使重流;改變元素的佈局,則會致使重繪和重流。大多數狀況下,瀏覽器會智能判斷,將重流和重繪只限制到相關的子樹上面,最小化所耗費的代價,而不會全局從新生成網頁。跨域
做爲開發者,應該儘可能設法下降重繪的次數和成本。好比,儘可能不要變更高層的 DOM 元素,而以底層 DOM 元素的變更代替;再好比重繪
table
佈局和flex
佈局,開銷都會比較大。瀏覽器
JavaScript引擎的主要做用是,讀取網頁中的JavaScript代碼,對其處理後運行。早期瀏覽器內部對 JavaScript 的處理過程以下:緩存
- 對代碼進行詞法分析,將代碼分解成詞元。
- 對詞元進行語法分析,將代碼整理成語法樹。
- 使用翻譯器,將代碼轉爲字節碼。
- 使用解釋器,將字節碼轉爲機器碼。
逐行所有解釋將字節碼轉爲機器碼,是很低效的。爲了提升運行速度,現代瀏覽器改成採用「即時編譯」(Just In Time compiler,縮寫 JIT),即字節碼只在運行時編譯,用到哪一行就編譯哪一行,而且把編譯結果緩存(inline cache)。一般一個程序被常常用到的只是其中一小部分代碼,有了緩存的編譯結果,整個程序的運行速度就會顯著提高。服務器
字節碼不能直接運行,而是運行在一個虛擬機之上,通常也把虛擬機稱爲JavaScript引擎。並不是全部的 JavaScript虛擬機運行時都有字節碼,有的JavaScript虛擬機基於源碼,即只要有可能,就經過 JIT編譯器直接把源碼編譯成機器碼運行,省略字節碼步驟。這樣作的目的,是爲了儘量地優化代碼、提升性能。
瀏覽器加載 JavaScript 腳本,主要經過<script>
元素完成。正常的網頁加載流程是這樣的:
- 渲染引擎一邊下載HTML網頁,一邊開始解析。
- 解析過程當中發現
<script>
元素後暫停解析,把網頁的控制權轉交給JavaScript引擎。- JavaScript引擎下載腳本後執行代碼。
- 執行完畢後,把網頁控制權交還渲染引擎,恢復往下解析 HTML 網頁。
瀏覽器會並行下載腳本,可是執行仍是按照定義的順序來執行。
較好的作法是將<script>
標籤都放在頁面底部而不是頭部的緣由以下:
對於來自同一個域名的資源,好比腳本文件、樣式表文件、圖片文件等,瀏覽器通常有限制,同時最多下載6~20個資源,即最多同時打開的 TCP 鏈接有限制,這是爲了防止對服務器形成太大壓力。若是是來自不一樣域名的資源,就沒有這個限制。因此,一般把靜態文件放在不一樣的域名之下,以加快下載速度。
在<script>
標籤中還常見以下屬性:
defer
屬性的做用是延遲腳本的執行,等到 DOM 加載生成後再執行腳本。該屬性的運行流程以下:
- 渲染引擎一邊下載HTML網頁,一邊開始解析。
- 解析過程當中,發現帶有
defer
屬性的<script>
標籤,繼續往下解析 HTML 網頁,同時開啓另外一個進程並行下載<script>
元素加載的外部腳本。- 渲染引擎完成解析 HTML 網頁,此時再交由JavaScript引擎執行已經下載完成的腳本。
defer
屬性能夠保證腳本下載的同時,瀏覽器繼續渲染。須要注意的是,一旦採用這個屬性,依舊保證腳本的執行順序。
async
屬性的做用是使用另外一個進程下載腳本,下載時不會阻塞渲染。該屬性的運行流程以下:
- 渲染引擎一邊下載HTML網頁,一邊開始解析。
- 解析過程當中,發現帶有
async
屬性的<script>
標籤,繼續往下解析 HTML 網頁,同時開啓另外一個進程並行下載<script>
標籤中的外部腳本。- 腳本下載完成,渲染引擎暫停解析 HTML 網頁,交由JavaScript引擎執行。
- 腳本執行完畢,JavaScript引擎交由渲染引擎恢復解析 HTML 網頁。
async
屬性能夠保證腳本下載的同時,瀏覽器繼續渲染。須要注意的是,一旦採用這個屬性,就沒法保證腳本的執行順序。
通常來講,若是腳本之間有依賴關係,就使用defer
屬性;若是腳本之間沒有依賴關係,就使用async
屬性。若是同時使用和defer
和async
屬性,瀏覽器行爲由async
屬性決定。
window
對象指當前的瀏覽器窗口。它也是當前頁面的頂層對象,即最高一層的對象,全部其餘對象都是它的下屬。
網頁對象屬性
window.document
:指向document
對象window.location
:指向Location
對象window.localStorage
:指向本地儲存的localStorage數據window.sessionStorage
:指向本地儲存的sessionStorage數據工具對象屬性:
window.locationbar
:地址欄對象,對象的visible
屬性是一個布爾值,表示該組件是否可見。window.menubar
:菜單欄對象,對象的visible
屬性是一個布爾值,表示該組件是否可見。window.toolbar
:工具欄對象,對象的visible
屬性是一個布爾值,表示該組件是否可見。window.statusbar
:狀態欄對象,對象的visible
屬性是一個布爾值,表示該組件是否可見。window.onload
屬性事件發生在文檔在瀏覽器窗口加載完畢時
Location
對象是瀏覽器提供的原生對象,提供URL相關的信息和操做方法。經過window.location
和document.location
屬性,能夠拿到這個對象。
Location.href
:整個 URL。若是對它寫入新的 URL 地址,瀏覽器會馬上跳轉到這個新地址Location.origin
:URL 的協議、主機名和端口。Location.protocol
:當前URL的協議,包括冒號Location.hostname
:當前URL的主機(不包括端口)。而Location.host包括端口Location.port
:當前URL的端口號Location.pathname
:URL 的路徑部分,從根路徑/
開始Location.search
:查詢字符串部分,從問號?
開始。encodeURI()
方法用於轉碼整個URL。它的參數是一個字符串,表明整個 URL。decodeURI()
方法用於整個URL的解碼。它接受一個參數,就是轉碼後的URL。Cookie是服務器保存在瀏覽器的一小段文本信息,瀏覽器每次向服務器發出請求,就會自動附上這段信息。不一樣瀏覽器對Cookie數量和大小的限制是不同的。通常來講單個域名設置的 Cookie 不該超過30個,每一個Cookie的大小不能超過4KB。超過限制之後Cookie將被忽略,不會被設置。
Cookie 有一些主要用途:
cookie除了使用K-V形式設置Cookie的名字Cookie的值外,它還有如下屬性:
Expires
屬性指定一個具體的到期時間,到了指定時間之後,瀏覽器就再也不保留這個Cookie。它的值是UTC格式,可使用Date.prototype.toUTCString()
進行格式轉換。若是不設置該屬性或設爲null
,Cookie只在當前會話有效,瀏覽器窗口一旦關閉該Cookie就會被刪除。Max-Age
屬性指定從如今開始Cookie存在的秒數,好比60 * 60 * 24 * 365
(即一年)。過了這個時間之後,瀏覽器就再也不保留這個 Cookie。若是同時指定了Expires
和Max-Age
,那麼Max-Age
的值將優先生效。Domain
屬性指定瀏覽器發出HTTP請求時,哪些域名要附帶這個Cookie。若是沒有指定該屬性,瀏覽器會默認將其設爲當前域名,這時子域名將不會附帶這個Cookie。若是指定了該屬性,那麼子域名也會附帶這個 Cookie。Path
屬性指定瀏覽器發出HTTP請求時,哪些路徑要附帶這個Cookie。只要瀏覽器發現,Path
屬性是HTTP請求路徑的開頭一部分,就會在頭信息裏面帶上這個Cookie。好比PATH
屬性是/
,那麼請求/docs
路徑也會包含它。Secure
屬性指定瀏覽器只有在加密協議 HTTPS 下,才能將這個 Cookie 發送到服務器。HttpOnly
屬性指定該Cookie沒法經過JS腳本拿到,主要讓document.cookie
、XMLHttpRequest
和Request API
拿不到該屬性。這樣就防止該Cookie被腳本讀到。只有瀏覽器發出HTTP請求時,纔會帶上該Cookie。cookie的生成
在瀏覽器保存Cookie,就要在HTTP迴應的頭信息裏面,放置一個或多個Set-Cookie
字段。一個Set-Cookie
字段可同時包括該cookie的多個屬性,沒有次序的要求。這個過程是服務端手動處理的。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
cookie的發送
向瀏覽器發送Cookie,就要在HTTP迴應的頭信息裏面,放置一個或多個Cookie
字段。一個Cookie
字段可同時包含多個 Cookie,使用分號分隔。這個過程是瀏覽器自動處理的。
Cookie: name=value; name2=value2; name3=value3
Storage接口用於腳本在瀏覽器保存數據,保存的數據都以「鍵值對」的形式存在。sessionStorage
保存的數據用於瀏覽器的一次會話,當會話結束數據被清空;localStorage
保存的數據長期存在,下一次訪問該網站的時候,網頁能夠直接讀取之前保存的數據。除了保存期限的長短不一樣,這兩個對象的其餘方面都一致。目前每一個域名的存儲上限視瀏覽器而定,Chrome是2.5MB,Firefox和Opera是5MB。
StorageEvent.storageArea
:返回鍵值對所在的整個對象。也就是說能夠從這個屬性上拿到當前域名儲存的全部鍵值對。StorageEvent.url
:表示原始觸發storage事件的那個網頁的網址。StorageEvent.key
:表示發生變更的鍵名。若是storage事件是由clear()
方法引發,該屬性返回null
。StorageEvent.newValue
:表示新的鍵值。若是storage事件是由clear()
方法引發,該屬性返回null
。StorageEvent.oldValue
:表示舊的鍵值。若是該鍵值對是新增的,該屬性返回null
。Storage.key()
接受一個整數做爲參數(從0開始),返回該位置對應的鍵值。Storage.getItem()
方法用於讀取數據。它只有一個參數:鍵名。若是鍵名不存在,該方法返回null
。Storage.setItem()
方法用於存入數據。它接受兩個參數:第一個是鍵名;第二個是保存的數據。若是鍵名已經存在,該方法會更新已有的鍵值。Storage.removeItem()
方法用於清除某個鍵名對應的鍵值。它接受鍵名做爲參數,若是鍵名不存在,該方法不會作任何事情。Storage.clear()
方法用於清除全部保存的數據。所謂「同源」指的是「三個相同」:
- 協議相同
- 域名相同
- 端口相同
常見的跨源通行方式有以下:
WebSocket是一種通訊協議,使用ws://
(非加密)和wss://
(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。具體使用狀況後續再補充。
JSONP是服務器與客戶端跨源通訊的經常使用方法。最大特色就是簡單易用,老式瀏覽器所有支持,服務端改造很是小。去電就是JSONP只能發GET
請求。
第一步,網頁添加一個<script>
元素,向服務器請求一個腳本,這不受同源政策限制,能夠跨域請求。
<script src="http://api.foo.com?callback=bar"></script>
注意請求的腳本網址有一個callback
參數(?callback=bar
),用來告訴服務器客戶端的回調函數名稱。
第二步,服務器收到請求後,拼接一個字符串,將JSON數據放在函數名裏面,做爲字符串返回(bar({...})
)。
第三步,客戶端會將服務器返回的字符串,做爲代碼解析。由於瀏覽器認爲這是script
標籤請求的腳本內容。這時客戶端只要定義了bar()
函數,就能在該函數體內,拿到服務器返回的JSON數據。做爲參數的JSON數據被視爲 JavaScript對象而不是字符串,所以避免了使用JSON.parse
的步驟。
function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); };
CORS是跨域資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標準,屬於跨源AJAX請求的根本解決方法。它容許瀏覽器向跨域的服務器發出XMLHttpRequest
請求,從而克服了AJAX只能同源使用的限制。相比JSONP只能發GET
請求,CORS容許任何類型的請求。
CORS通訊與普通的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨域,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求。所以實現 CORS 通訊的關鍵是服務器。只要服務器實現了CORS接口就可跨域通訊。
CORS 請求分紅兩類:簡單請求和非簡單請求。只要同時知足如下兩大條件就屬於簡單請求,凡是不一樣時知足上面兩個條件就屬於非簡單請求。非簡單請求是那種對服務器提出特殊要求的請求,好比請求方法是PUT
或DELETE
,或者Content-Type
字段的類型是application/json
。
請求方法是如下三種方法之一:
- HEAD
- GET
- POST
HTTP的頭信息不超出如下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
簡單請求
簡單請求發送:
對於簡單請求,瀏覽器直接發出CORS請求。具體來講就是在頭信息之中,增長一個Origin
字段。
GET /cors HTTP/1.1 Host: api.alice.com Origin: http://api.bob.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
簡單請求響應:
服務器收到查詢請求之後,檢查了Origin
字段之後,確認容許跨源請求就能夠作出迴應,返回的響應會多出幾個頭信息字段。若是服務器否認了查詢請求,會返回一個正常的HTTP迴應,可是沒有任何CORS相關的頭信息字段,或者明確表示請求不符合條件。瀏覽器發現這個迴應的頭信息沒有包含Access-Control-Allow-Origin
字段就知道出錯了,從而拋出一個錯誤。該錯誤被XMLHttpRequest
的onerror
回調函數捕獲。注意這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
上面的頭信息之中,有三個與CORS請求相關的字段,都以Access-Control-
開頭:
Access-Control-Allow-Origin
:該字段是必須的。它的值要麼是請求時Origin
字段的值,要麼是一個*
來表示接受任意域名的請求。注意若是服務器要求瀏覽器發送Cookie,該字段就不能設爲星號,必須指定明確的、與請求網頁一致的域名。Access-Control-Allow-Credentials
:該字段可選。表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true
即表示服務器明確許可,瀏覽器能夠把Cookie包含在請求中,一塊兒發給服務器。設爲true
同時要求開發者必須在AJAX請求中打開withCredentials
屬性,不然即便服務器要求發送 Cookie,瀏覽器也不會發送。Access-Control-Expose-Headers
:該字段可選。CORS請求時XMLHttpRequest
對象的getResponseHeader()
方法只能拿到服務器返回頭的6個基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers
裏面指定。上面的例子指定getResponseHeader('FooBar')
能夠返回FooBar
字段的值。
非簡單請求
非簡單請求發送:
非簡單請求的CORS請求,會在正式通訊以前增長一次HTTP查詢請求。瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP方法和頭信息字段。只有獲得確定答覆後瀏覽器纔會發出正式的XMLHttpRequest
請求,不然就報錯。
這是爲了防止這些新增的請求,對傳統的沒有CORS支持的服務器造成壓力,給服務器一個提早拒絕的機會,這樣能夠防止服務器收到大量
DELETE
和PUT
請求,這些傳統的表單不可能跨域發出的請求。
XMLHttpRequest
對象是AJAX的主要接口,用於瀏覽器與服務器之間的通訊。儘管名字裏面有XML
和Http
,它實際上可使用多種協議(好比file
或ftp
),發送任何格式的數據(包括字符串和二進制)。下面是XMLHttpRequest
對象簡單用法的完整例子:
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ // 通訊成功時,狀態值爲4 if (xhr.readyState === 4){ if (xhr.status === 200){ console.log(xhr.responseText); } else { console.error(xhr.statusText); } } }; xhr.onerror = function (e) { console.error(xhr.statusText); }; xhr.open('GET', '/endpoint', true); xhr.send(null);
XMLHttpRequest.withCredentials
屬性是一個布爾值,表示跨域請求時用戶信息(好比Cookie和認證的HTTP頭信息)是否會包含在請求之中,默認爲false
,即向example.com
發出跨域請求時,不會發送example.com
設置在本機上的Cookie(若是有的話)。若是須要跨域AJAX請求發送Cookie,須要withCredentials
屬性設爲true
。注意同源的請求不須要設置這個屬性。
XMLHttpRequest.upload
屬性返回一個對象,表示上傳文件的相關事件。主要就是監聽這個對象的各類事件。
XMLHttpRequest.timeout
屬性返回一個整數,表示多少毫秒後,若是請求仍然沒有獲得結果就會自動終止。若是該屬性等於0,就表示沒有時間限制。
XMLHttpRequest.readyState
返回一個整數,表示實例對象的當前狀態。它可能返回如下值:
- 0,表示XMLHttpRequest實例已經生成,可是實例的
open()
方法尚未被調用。- 1,表示
open()
方法已經調用,可是實例的send()
方法尚未調用,仍然可使用實例的setRequestHeader()
方法,設定HTTP請求的頭信息。- 2,表示實例的
send()
方法已經調用,而且服務器返回的頭信息和狀態碼已經收到。- 3,表示正在接收服務器傳來的數據體(body 部分)。這時若是實例的
responseType
屬性等於text
或者空字符串,responseText
屬性就會包含已經收到的部分信息。- 4,表示服務器返回的數據已經徹底接收,或者本次接收已經失敗。
XMLHttpRequest.status
屬性返回一個整數,表示服務器迴應的HTTP狀態碼。
XMLHttpRequest.responseType
屬性是一個字符串,表示服務器返回數據的類型。
XMLHttpRequest.response
屬性表示服務器返回的數據體(即HTTP迴應的 body 部分),它多是任何數據類型(如字符串、對象、二進制對象等)。
XMLHttpRequest.responseURL
屬性是字符串,表示發送數據的服務器的網址。
XMLHttpRequest.responseText
屬性返回從服務器接收到的字符串。只有HTTP請求完成接收之後,該屬性纔會包含完整的數據。
XMLHttpRequest.ontimeout
:timeout事件的監聽函數XMLHttpRequest.onloadstart
:loadstart事件(請求發出)的監聽函數XMLHttpRequest.onprogress
:progress事件(正在發送和加載數據)的監聽函數XMLHttpRequest.onloadend
:loadend 事件(請求完成,無論成功或失敗)的監聽函數XMLHttpRequest.onabort
:abort 事件(請求停止,如用戶調用了abort()
方法)的監聽函數XMLHttpRequest.onerror
:error 事件(請求失敗)的監聽函數XMLHttpRequest.onload
:load事件(請求成功完成)的監聽函數XMLHttpRequest.onreadystatechange
:readystatechange事件(實例的readyState
屬性變化)的監聽函數XMLHttpRequest.open()
方法用於指定HTTP請求的參數,或者說初始化XMLHttpRequest實例對象。注意若是對使用過open()
方法的AJAX請求,再次使用這個方法,等同於調用abort()
,即終止請求。XMLHttpRequest.overrideMimeType()
方法用來指定MIME類型,覆蓋服務器返回的MIME類型,從而讓瀏覽器進行不同的處理。好比服務器返回的數據類型是text/xml
,因爲種種緣由瀏覽器解析不成功報錯,這時就拿不到數據。爲了拿到原始數據能夠把MIME類型改爲text/plain
,這樣瀏覽器就不會去自動解析,從而可拿到原始文本。注意該方法必須在send()
方法以前調用。XMLHttpRequest.setRequestHeader()
方法用於設置瀏覽器發送的HTTP請求的頭信息。若是該方法屢次調用且設定同一個字段,則每一次調用的值會被合併成一個單一的值發送。注意該方法必須在send()
方法以前調用。XMLHttpRequest.getResponseHeader()
方法返回HTTP頭信息指定字段的值,若是尚未收到服務器迴應或者指定字段不存在,則返回null
。XMLHttpRequest.send()
方法用於實際發出HTTP請求。XMLHttpRequest.abort()
方法用來終止已經發出的HTTP請求。調用這個方法之後,readyState
屬性變爲4,status
屬性變爲0。至於其餘的還有File對象、Blob對象、ArrayBuffer對象等,到時候隨用隨查吧。