瀏覽器與新技術
這是一篇很長的文章,能夠算上一本小書了,有精力的很是建議閱讀。css
常見的瀏覽器內核有哪些?
瀏覽器/RunTime | 內核(渲染引擎) | JavaScript 引擎 |
---|---|---|
Chrome | Blink(28~) Webkit(Chrome 27) |
V8 |
FireFox | Gecko | SpiderMonkey |
Safari | Webkit | JavaScriptCore |
Edge | EdgeHTML | Chakra(for JavaScript) |
IE | Trident | Chakra(for JScript) |
PhantomJS | Webkit | JavaScriptCore |
Node.js | - | V8 |
瀏覽器的主要組成部分是什麼?
- 用戶界面 - 包括地址欄、前進/後退按鈕、書籤菜單等。除了瀏覽器主窗口顯示的您請求的頁面外,其餘顯示的各個部分都屬於用戶界面。
- 瀏覽器引擎 - 在用戶界面和呈現引擎之間傳送指令。
- 呈現引擎 - 負責顯示請求的內容。若是請求的內容是 HTML,它就負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上。
- 網絡 - 用於網絡調用,好比 HTTP 請求。其接口與平臺無關,併爲全部平臺提供底層實現。
- 用戶界面後端 - 用於繪製基本的窗口小部件,好比組合框和窗口。其公開了與平臺無關的通用接口,而在底層使用操做系統的用戶界面方法。
- JavaScript 解釋器。用於解析和執行 JavaScript 代碼。
- 數據存儲。這是持久層。瀏覽器須要在硬盤上保存各類數據,例如 Cookie。新的 HTML 規範 (HTML5) 定義了「網絡數據庫」,這是一個完整(可是輕便)的瀏覽器內數據庫。
圖:瀏覽器的主要組件。html
值得注意的是,和大多數瀏覽器不一樣,Chrome 瀏覽器的每一個標籤頁都分別對應一個呈現引擎實例。每一個標籤頁都是一個獨立的進程。前端
瀏覽器是如何渲染UI的?
- 瀏覽器獲取HTML文件,而後對文件進行解析,造成DOM Tree
- 與此同時,進行CSS解析,生成Style Rules
- 接着將DOM Tree與Style Rules合成爲 Render Tree
- 接着進入佈局(Layout)階段,也就是爲每一個節點分配一個應出如今屏幕上的確切座標
- 隨後調用GPU進行繪製(Paint),遍歷Render Tree的節點,並將元素呈現出來
瀏覽器如何解析css選擇器?
瀏覽器會『從右往左』解析CSS選擇器。html5
咱們知道DOM Tree與Style Rules合成爲 Render Tree,其實是須要將Style Rules附着到DOM Tree上,所以須要根據選擇器提供的信息對DOM Tree進行遍歷,才能將樣式附着到對應的DOM元素上。java
如下這段css爲例node
.mod-nav h3 span {font-size: 16px;} 複製代碼
咱們對應的DOM Tree 以下nginx
若從左向右的匹配,過程是:git
- 從 .mod-nav 開始,遍歷子節點 header 和子節點 div
- 而後各自向子節點遍歷。在右側 div 的分支中
- 最後遍歷到葉子節點 a ,發現不符合規則,須要回溯到 ul 節點,再遍歷下一個 li-a,一顆DOM樹的節點動不動上千,這種效率很低。
若是從右至左的匹配:github
- 先找到全部的最右節點 span,對於每個 span,向上尋找節點 h3
- 由 h3再向上尋找 class=mod-nav 的節點
- 最後找到根元素 html 則結束這個分支的遍歷。
後者匹配性能更好,是由於從右向左的匹配在第一步就篩選掉了大量的不符合條件的最右節點(葉子節點);而從左向右的匹配規則的性能都浪費在了失敗的查找上面。web
DOM Tree是如何構建的?
- 轉碼: 瀏覽器將接收到的二進制數據按照指定編碼格式轉化爲HTML字符串
- 生成Tokens: 以後開始parser,瀏覽器會將HTML字符串解析成Tokens
- 構建Nodes: 對Node添加特定的屬性,經過指針肯定 Node 的父、子、兄弟關係和所屬 treeScope
- 生成DOM Tree: 經過node包含的指針肯定的關係構建出DOM Tree
瀏覽器重繪與重排的區別?
- 重排: 部分渲染樹(或者整個渲染樹)須要從新分析而且節點尺寸須要從新計算,表現爲從新生成佈局,從新排列元素
- 重繪: 因爲節點的幾何屬性發生改變或者因爲樣式發生改變,例如改變元素背景色時,屏幕上的部份內容須要更新,表現爲某些元素的外觀被改變
單單改變元素的外觀,確定不會引發網頁從新生成佈局,但當瀏覽器完成重排以後,將會從新繪製受到這次重排影響的部分
重排和重繪代價是高昂的,它們會破壞用戶體驗,而且讓UI展現很是遲緩,而相比之下重排的性能影響更大,在二者沒法避免的狀況下,通常咱們寧肯選擇代價更小的重繪。
『重繪』不必定會出現『重排』,『重排』必然會出現『重繪』。
如何觸發重排和重繪?
任何改變用來構建渲染樹的信息都會致使一次重排或重繪:
- 添加、刪除、更新DOM節點
- 經過display: none隱藏一個DOM節點-觸發重排和重繪
- 經過visibility: hidden隱藏一個DOM節點-只觸發重繪,由於沒有幾何變化
- 移動或者給頁面中的DOM節點添加動畫
- 添加一個樣式表,調整樣式屬性
- 用戶行爲,例如調整窗口大小,改變字號,或者滾動。
如何避免重繪或者重排?
集中改變樣式
咱們每每經過改變class的方式來集中改變樣式
// 判斷是不是黑色系樣式 const theme = isDark ? 'dark' : 'light' // 根據判斷來設置不一樣的class ele.setAttribute('className', theme) 複製代碼
使用DocumentFragment
咱們能夠經過createDocumentFragment建立一個遊離於DOM樹以外的節點,而後在此節點上批量操做,最後插入DOM樹中,所以只觸發一次重排
var fragment = document.createDocumentFragment(); for (let i = 0;i<10;i++){ let node = document.createElement("p"); node.innerHTML = i; fragment.appendChild(node); } document.body.appendChild(fragment); 複製代碼
提高爲合成層
將元素提高爲合成層有如下優勢:
- 合成層的位圖,會交由 GPU 合成,比 CPU 處理要快
- 當須要 repaint 時,只須要 repaint 自己,不會影響到其餘的層
- 對於 transform 和 opacity 效果,不會觸發 layout 和 paint
提高合成層的最好方式是使用 CSS 的 will-change 屬性:
#target { will-change: transform; } 複製代碼
前端如何實現即時通信?
短輪詢
短輪詢的原理很簡單,每隔一段時間客戶端就發出一個請求,去獲取服務器最新的數據,必定程度上模擬實現了即時通信。
- 優勢:兼容性強,實現很是簡單
- 缺點:延遲性高,很是消耗請求資源,影響性能
comet
comet有兩種主要實現手段,一種是基於 AJAX 的長輪詢(long-polling)方式,另外一種是基於 Iframe 及 htmlfile 的流(streaming)方式,一般被叫作長鏈接。
長輪詢優缺點:
- 優勢:兼容性好,資源浪費較小
- 缺點:服務器hold鏈接會消耗資源,返回數據順序無保證,難於管理維護
長鏈接優缺點:
- 優勢:兼容性好,消息即時到達,不發無用請求
- 缺點:服務器維護長鏈接消耗資源
SSE
SSE(Server-Sent Event,服務端推送事件)是一種容許服務端向客戶端推送新數據的HTML5技術。
- 優勢:基於HTTP而生,所以不須要太多改造就能使用,使用方便,而websocket很是複雜,必須藉助成熟的庫或框架
- 缺點:基於文本傳輸效率沒有websocket高,不是嚴格的雙向通訊,客戶端向服務端發送請求沒法複用以前的鏈接,須要從新發出獨立的請求
Websocket
Websocket是一個全新的、獨立的協議,基於TCP協議,與http協議兼容、卻不會融入http協議,僅僅做爲html5的一部分,其做用就是在服務器和客戶端之間創建實時的雙向通訊。
- 優勢:真正意義上的實時雙向通訊,性能好,低延遲
- 缺點:獨立與http的協議,所以須要額外的項目改造,使用複雜度高,必須引入成熟的庫,沒法兼容低版本瀏覽器
Web Worker
後面性能優化部分會用到,先作了解
Web Worker 的做用,就是爲 JavaScript 創造多線程環境,容許主線程建立 Worker 線程,將一些任務分配給後者運行
Service workers
後面性能優化部分會用到,先作了解
Service workers 本質上充當Web應用程序與瀏覽器之間的代理服務器,也能夠在網絡可用時做爲瀏覽器和網絡間的代理,建立有效的離線體驗。
什麼是瀏覽器同源策略?
同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。
同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。
下表給出了相對http://store.company.com/dir/page.html同源檢測的示例:
瀏覽器中的大部份內容都是受同源策略限制的,可是如下三個標籤能夠不受限制:
<img src=XXX>
<link href=XXX>
<script src=XXX>
如何實現跨域?
跨域是個比較古老的命題了,歷史上跨域的實現手段有不少,咱們如今主要介紹三種比較主流的跨域方案,其他的方案咱們就不深刻討論了,由於使用場景不多,也不必記這麼多奇技淫巧。
最經典的跨域方案jsonp
jsonp本質上是一個Hack,它利用<script>
標籤不受同源策略限制的特性進行跨域操做。
jsonp優勢:
- 實現簡單
- 兼容性很是好
jsonp的缺點:
- 只支持get請求(由於
<script>
標籤只能get) - 有安全性問題,容易遭受xss攻擊
- 須要服務端配合jsonp進行必定程度的改造
jsonp的實現:
function JSONP({ url, params, callbackKey, callback }) { // 在參數裏制定 callback 的名字 params = params || {} params[callbackKey] = 'jsonpCallback' // 預留 callback window.jsonpCallback = callback // 拼接參數字符串 const paramKeys = Object.keys(params) const paramString = paramKeys .map(key => `${key}=${params[key]}`) .join('&') // 插入 DOM 元素 const script = document.createElement('script') script.setAttribute('src', `${url}?${paramString}`) document.body.appendChild(script) } JSONP({ url: 'http://s.weibo.com/ajax/jsonp/suggestion', params: { key: 'test', }, callbackKey: '_cb', callback(result) { console.log(result.data) } }) 複製代碼
最流行的跨域方案cors
cors是目前主流的跨域解決方案,跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
若是你用express,能夠這樣在後端設置
//CORS middleware var allowCrossDomain = function(req, res, next) { res.header('Access-Control-Allow-Origin', 'http://example.com'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type'); next(); } //... app.configure(function() { app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({ secret: 'cool beans' })); app.use(express.methodOverride()); app.use(allowCrossDomain); app.use(app.router); app.use(express.static(__dirname + '/public')); }); 複製代碼
在生產環境中建議用成熟的開源中間件解決問題。
最方便的跨域方案Nginx
nginx是一款極其強大的web服務器,其優勢就是輕量級、啓動快、高併發。
如今的新項目中nginx幾乎是首選,咱們用node或者java開發的服務一般都須要通過nginx的反向代理。
反向代理的原理很簡單,即全部客戶端的請求都必須先通過nginx的處理,nginx做爲代理服務器再講請求轉發給node或者java服務,這樣就規避了同源策略。
#進程, 可更具cpu數量調整
worker_processes 1;
events {
#鏈接數
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#鏈接超時時間,服務器會在這個時間事後關閉鏈接。
keepalive_timeout 10;
# gizp壓縮
gzip on;
# 直接請求nginx也是會報跨域錯誤的這裏設置容許跨域
# 若是代理地址已經容許跨域則不須要這些, 不然報錯(雖然這樣nginx跨域就沒意義了)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
# srever模塊配置是http模塊中的一個子模塊,用來定義一個虛擬訪問主機
server {
listen 80;
server_name localhost;
# 根路徑指到index.html
location / {
root html;
index index.html index.htm;
}
# localhost/api 的請求會被轉發到192.168.0.103:8080
location /api {
rewrite ^/b/(.*)$ /$1 break; # 去除本地接口/api前綴, 不然會出現404
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.0.103:8080; # 轉發地址
}
# 重定向錯誤頁面到/50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
複製代碼
其它跨域方案
- HTML5 XMLHttpRequest 有一個API,postMessage()方法容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞。
- WebSocket 是一種雙向通訊協議,在創建鏈接以後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據,鏈接創建好了以後 client 與 server 之間的雙向通訊就與 HTTP 無關了,所以能夠跨域。
- window.name + iframe:window.name屬性值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值,咱們能夠利用這個特色進行跨域。
- location.hash + iframe:a.html欲與c.html跨域相互通訊,經過中間頁b.html來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。
- document.domain + iframe: 該方式只能用於二級域名相同的狀況下,好比 a.test.com 和 b.test.com 適用於該方式,咱們只須要給頁面添加 document.domain ='test.com' 表示二級域名都相同就能夠實現跨域,兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。
1.若是以爲這篇文章還不錯,來個分享、點贊吧,讓更多的人也看到