web前端效能優化 -- 從瀏覽器輸入url到頁面載入完成全過程解析
本文將從瀏覽器輸入url到頁面載入完成中經歷的各個階段來探討web前端效能可以優化的點
1、瀏覽器輸入URL並按下回車
無優化點
2、瀏覽器查詢當前URL是否存在快取
徹底弄懂HTTP快取機制及原理
傳送門: https://www.cnblogs.com/chenq...
優化點
伺服器對靜態資源設定瀏覽器快取資訊,瀏覽器在有快取的情況下直接從本地讀取資源。
3、DNS域名解析
位址列輸入的域名並不是最後資源所在的真實位置,域名只是與IP地址的一個對映。網路伺服器的IP地址那麼多,我們不可能去記一串串的數字,因此域名就產生了,域名解析的過程實際是將域名還原為IP地址的過程。
順便介紹下,無優化點
4、TCP連線
DNS解析後得到了伺服器的ip,接下來就是和伺服器建立起連線,這通過TCP的三次握手完成。
具體為:
瀏覽器:你好~你在嗎~我能和你聊會天嗎
伺服器:嗯!我在~我們聊會吧~
瀏覽器:好的,那我們開始咯
順便介紹下,無優化點,順便再說下四次告別
TCP連線後資料的傳輸是雙向的,那麼當瀏覽器不想說話了,就會發出中止連線的訊號標誌,伺服器收到後會回一個確認標誌給瀏覽器,這時瀏覽器就不能再向伺服器傳輸資料了,但是還可以傳送訊號。為了對伺服器的尊重,等伺服器把話說完後才會發一個結束標誌給瀏覽器,這時瀏覽器知道伺服器也向自己傳輸完資料了,回一個確認標誌過去,才真正結束這次TCP連線。
5、瀏覽器向伺服器傳送HTTP請求
完整的HTTP請求包含請求起始行、請求頭部、請求主體三部分。快取資訊是儲存在請求頭中,在階段二上的連線有介紹。
優化點
減少請求次數
- 合併外部請求的js、css檔案
- 對icon檔案進行處理。運用CSS精靈合併處理多個icon檔案、運用圖示字型、把小圖示轉為base64等
6、瀏覽器接收響應體
伺服器在收到瀏覽器傳送的HTTP請求之後,會將收到的HTTP報文封裝成HTTP的Request物件,並通過不同的Web伺服器進行處理,處理完的結果以HTTP的Response物件返回,主要包括狀態碼,響應頭,響應報文三個部分。響應體為伺服器返回給瀏覽器的資訊,主要由HTML,css,js,圖片檔案組成。
優化點
壓縮響應體
- 對請求的檔案進行打包(webpack),減少檔案體積
7、頁面渲染
頁面是如何渲染的
傳送門: https://www.cnblogs.com/tootw...
js的影響
js是會阻塞頁面渲染的,那麼解決方法有很多,可以把js放在body的底部,或者是非同步載入js。
同步非同步載入詳解傳送門: https://blog.csdn.net/qq_3498...
淺談script標籤的defer和async傳送門: https://segmentfault.com/a/11...
css的影響
--------------------- 作者:JiajiaAz 來源:CSDN 原文: https://blog.csdn.net/qq_3265...
優化點
圖片懶載入
概念:
訪問頁面時,先把img元素的背景圖片src替換成一張佔位圖,這樣只需請求一次,當圖片出現在瀏覽器的可視區域內時,再設定圖片的真實路徑,顯示圖片。
方法:
頁面中的img元素,若沒有src屬性,瀏覽器就不會發出請求去下載圖片,只有通過Javascript設定了圖片路徑,瀏覽器才會傳送請求。
1)懶載入先在頁面中把需要延遲載入的圖片統一使用一張佔位圖進行佔位,把真正的路徑存在元素“data-url”屬性裡。
2)頁面載入完成後,通過scrollTop判斷圖片是否在使用者的視野,如果在,則將 data-url的值取出來存放到src中。
vue/react專案頁面渲染優化
在vue或者react的專案下,怎麼解決頁面渲染問題呢,畢竟初始的html就是一個空白頁面,頁面渲染全靠js的render,如果這個入口js過大,必然會導致頁面白屏時間過長。
如果可以對入口的js進行程式碼分割,把後期才會用到的js先獨立出來,等用時再引入那麼就可以大大減少初始js的體積了,首屏頁面渲染起來自然也快。
非同步載入js的概念
<button>click to load js</button> <script> document.querySelector('button').onclick = () => { let loadJs = document.createElement('script') loadJs.src = './load.js' document.body.appendChild(loadJs) loadJs.onload = () => { alert("finish loading!") } } </script>
嗯簡單來說原理就是這麼實現的,設定個觸發點,觸發後建立個src為需要引進js路徑的script Dom,然後設定下載入後的回撥函式..
webpack上的實現方法
寫在主入口main.js中:point_down:
document.querySelector('button').onclick = () => { require.ensure([], ()=> { // 引入非同步載入的js let loadJS = require('./asyncJS') alert(loadJS.flag) }, 'asyncJS') }
webpack的配置檔案:point_down:
output: { path: path.resolve(__dirname, './dist'), filename: '[name].bundle.js', publicPath: '../dist/', chunkFilename: 'chunks/[name]-[hash].js' }
webpack 在編譯時,會靜態地解析程式碼中的require.ensure(),同時將模組新增到一個分開的 chunk 當中。這個新的 chunk 會被 webpack 通過 jsonp來按需載入。
語法如下:
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
依賴 - dependencies
這是一個字串陣列,通過這個引數,在所有的回撥函式的程式碼被執行前,我們可以將所有需要用到的模組進行宣告。
回撥 - callback
當所有的依賴都載入完成後,webpack會執行這個回撥函式。require物件的一個實現會作為一個引數傳遞給這個回撥函式。因此,我們可以進一步 require() 依賴和其它模組提供下一步的執行。
chunk名稱 - chunkName
chunkName 是提供給這個特定的 require.ensure() 的 chunk的名稱。通過提供 require.ensure() 不同執行點相同的名稱,我們可以保證所有的依賴都會一起放進相同的 檔案束(bundle)。
打包出來的目錄:point_down:
其中chunks資料夾裡的內容就是從main.js中分離出來的,會在被需要時再引入到專案中。
頁面載入時是這樣:point_down:
點了按鈕就是這樣了:point_down:
順便介紹下require.ensure() 的坑點
空陣列作為引數 require.ensure([], function(require){
require('./a.js'); }); 以上程式碼保證了拆分點被建立,而且 a.js 被 webpack 分開打包。
依賴作為引數 require.ensure(['./a.js'], function(require) {
require('./b.js'); }); 上面程式碼, a.js 和 b.js 都被打包到一起,而且從主檔案束中拆分出來。但只有 b.js 的內容被執行。a.js 的內容僅僅是可被使用,但並沒有被輸出。
想去執行 a.js,我們需要非同步地引用它,如 require('./a.js'),讓它的 JavaScritp 被執行。
vue/react專案中的實現方法
說了這麼多,繞回來本題中心,這裡以vue為例子來實現按需非同步載入功能。按需載入什麼呢?當然是元件啦!
在大型應用中,我們可能需要將應用分割成小一些的程式碼塊,並且只在需要的時候才從伺服器載入一個模組。為了簡化,Vue
允許你以一個工廠函式的方式定義你的元件,這個工廠函式會非同步解析你的元件定義。Vue
只有在這個元件需要被渲染的時候才會觸發該工廠函式,且會把結果快取起來供未來重渲染。例如:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 向 `resolve` 回撥傳遞元件定義 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
如你所見,這個工廠函式會收到一個 resolve 回撥,這個回撥函式會在你從伺服器得到元件定義的時候被呼叫。你也可以呼叫 reject(reason) 來表示載入失敗。
結合前面講的直接上例子,基於非同步元件我們可以實現vue的非同步路由:point_down:
export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: resolve => { require.ensure([], () => { resolve(require('@/components/HelloWorld.vue')) }) } }, { path: '/other', name: 'other', component: resolve => { require.ensure([], () => { resolve(require('@/components/Other.vue')) }) } } ] })
當然require.ensure這種寫法比較舊,就是對webpack的相容性會好點,現在結合es6的寫法會更加簡潔
你也可以在工廠函式中返回一個 Promise,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:
Vue.component( 'async-webpack-example', // 這個 `import` 函式會返回一個 `Promise` 物件。 () => import('./my-async-component') )
export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: () => import('@/components/HelloWorld.vue') }, { path: '/other', name: 'Other', component: () => import('@/components/Other.vue') } ] })
嗯這樣就行了
順便也提下如何處理載入狀態
這裡的非同步元件工廠函式也可以返回一個如下格式的物件:
const AsyncComponent = () => ({ // 需要載入的元件 (應該是一個 `Promise` 物件) component: import('./MyComponent.vue'), // 非同步元件載入時使用的元件 loading: LoadingComponent, // 載入失敗時使用的元件 error: ErrorComponent, // 展示載入時元件的延時時間。預設值是 200 (毫秒) delay: 200, // 如果提供了超時時間且元件載入也超時了, // 則使用載入失敗時使用的元件。預設值是:`Infinity` timeout: 3000 })