拋出一個問題,從輸入
url
地址欄到全部內容顯示到界面上作了哪些事?
DNS
服務器請求解析該 URL 中的域名所對應的 IP
地址;TCP
鏈接(三次握手);URL
中域名後面部分對應的文件)的HTTP
請求,該請求報文做爲 TCP
三次握手的第三個報文的數據發送給服務器;html
文本並顯示內容;TCP
鏈接(四次揮手);上面這個問題是一個面試官很是喜歡問的問題,咱們下面把這6個步驟分解,逐步細談優化。
DNS
解析DNS`解析:將域名解析爲ip地址 ,由上往下匹配,只要命中便中止css
優化策略:儘可能容許使用瀏覽器的緩存,能給咱們節省大量時間,下面有dns-prefetch
的介紹,每次dns
解析大概須要20-120秒
TCP
的三次握手SYN (同步序列編號)ACK(確認字符)html
優化策略:
HTTP
協議通訊最耗費時間的是創建TCP
鏈接的過程,那咱們就可使用HTTP Keep-Alive
,在HTTP
早期,每一個HTTP
請求都要求打開一個TCP socket
鏈接,而且使用一次以後就斷開這個TCP
鏈接。 使用keep-alive
能夠改善這種狀態,即在一次TCP鏈接中能夠持續發送多份數據而不會斷開鏈接。經過使用keep-alive
機制,能夠減小TCP
鏈接創建次數,也意味着能夠減小TIME_WAIT
狀態鏈接,以此提升性能和提升http
服務器的吞吐率(更少的tcp
鏈接意味着更少的系統內核調用keep-alive
並非免費的午飯,長時間的TCP
鏈接容易致使系統資源無效佔用。配置不當的keep-alive
,有時比重複利用鏈接帶來的損失還更大。因此,正確地設置keep-alive timeout
時間很是重要。(這個keep-alive_timout
時間值意味着:一個http
產生的tcp
鏈接在傳送完最後一個響應後,還須要hold
住keepalive_timeout
秒後,纔開始關閉這個鏈接),若是想更詳細瞭解能夠看這篇文章keep-alve性能優化的測試結果 webScoket
通訊協議,僅一次TCP
握手就一直保持鏈接,並且他對二進制數據的傳輸有更好的支持,能夠應用於即時通訊,海量高併發場景。webSocket的原理以及詳解 HTTP
請求次數,每次HTTP
請求都會有請求頭,返回響應都會有響應頭,屢次請求不只浪費時間並且會讓網絡傳輸不少無效的資源,使用前端模塊化技術 AMD CMD commonJS ES6等模塊化方案
將多個文件壓縮打包成一個,固然也不能都放在一個文件中,由於這樣傳輸起來可能會很慢,權衡取一箇中間值cookie
去辨識用戶的多種情況時,使用session
替代,把數據儲存在服務器端或者服務器端的數據庫中,這樣只須要一個cookie
傳輸,節省大量的無效傳輸,並且儲存的數據能夠是永久無線大的。preload
和dns-prefetch
、prefetch
,預請求資源,這種請求方式不會阻塞瀏覽器的解析,並且能將預請求的資源緩存起來,並且能夠設置crossorgin
進行跨域資源的緩存,不會推遲首屏的渲染時間,還會加快後面的加載時間,由於後面的自己須要的資源會直接從緩存中讀取,而不會走網絡請求。defer
和async
屬性的腳本,異步加載的方式,會先發請求,而後JS
引擎繼續解析下面的內容。async
的屬性腳本會無序加載,誰先請求回來就馬上加載誰,當請求回來的時候,不管是在DOM
解析仍是腳本的解析,接下來都先會解析這個asncy
腳本,它會阻塞DOM
的解析。defer
屬性的會按HTML
結構的按順序加載,在DOMContentLoad
前加載,可是加載以前全部的DOM
解析確定已經完成了,defer
屬性的腳本不會阻塞DOM
的解析,它也叫延遲腳本。因爲實際中它不肯定是否在DOMContentLoaded
前加載,因此通常只放一個defer
的腳本,參考移動端京東網頁。 async和defer詳解
一直沒想到這裏使用什麼優化手段,今晚想到了,使用
Nginx
反向代理服務器,主要是對服務器端的優化。
Nginx
是一款輕量級的Web
服務器/反向代理服務器及電子郵件(IMAP/POP3
)代理服務器,並在一個BSD-like
協議下發行。其特色是佔有內存少,併發能力強,事實上nginx的併發能力確實在同類型的網頁服務器中表現較好,中國大陸使用nginx
網站用戶有:百度、京東、新浪、網易、騰訊、淘寶等。 Nginx
是一個安裝很是的簡單、配置文件很是簡潔(還可以支持perl
語法)、Bug
很是少的服務。Nginx
啓動特別容易,而且幾乎能夠作到7*24不間斷運行,即便運行數個月也不須要從新啓動。你還可以不間斷服務的狀況下進行軟件版本的升級。Nginx
如何實現負載均衡前端
Upstream指定後端服務器地址列表 upstream balanceServer { server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; } 複製代碼在server中攔截響應請求,並將請求轉發到Upstream中配置的服務器列表。 server { server_name fe.server.com; listen 80; location /api { proxy_pass http://balanceServer; } }
將請求優先分配給壓力較小的服務器,它能夠平衡每一個隊列的長度,並避免向壓力大的服務器添加更多的請求。java
upstream balanceServer { least_conn; //配置壓力較小的服務器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
upstream balanceServer { fair; //配置響應時間最短的服務器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
來自同一個ip的請求永遠只分配一臺服務器,有效解決了動態網頁存在的session共享問題。 upstream balanceServer { ip_hash; //配置1個IP永遠只分配一臺服務器 server 10.1.22.33:12345; server 10.1.22.34:12345; server 10.1.22.35:12345; }
location ~* \.(png|gif|jpg|jpeg)$ { root /root/static/; autoindex on; access_log off; expires 10h;# 設置過時時間爲10小時 } 複製代碼匹配以png|gif|jpg|jpeg爲結尾的請求, 並將請求轉發到本地路徑,root中指定的路徑即nginx 本地路徑。同時也能夠進行一些緩存的設置。
Nginx
解決跨域nginx解決跨域的原理 例如: 前端server的域名爲:fe.server.com 後端服務的域名爲:dev.server.com 如今我在fe.server.com對dev.server.com發起請求必定會出現跨域。 如今咱們只須要啓動一個nginx服務器,將server_name設置爲fe.server.com, 而後設置相應的location以攔截前端須要跨域的請求,最後將請求代理回dev.server.com。 以下面的配置: server { listen 80; server_name fe.server.com; location / { proxy_pass dev.server.com; } } 複製代碼這樣能夠完美繞過瀏覽器的同源策略:fe.server.com訪問nginx的fe.server.com 屬於同源訪問,而nginx對服務端轉發的請求不會觸發瀏覽器的同源策略。
最重要的一點來了,如今的BATJ
大都使用了這種配置:react
配置GZIPwebpack
GZIP
是規定的三種標準HTTP
壓縮格式之一。目前絕大多數的網站都在使用GZIP
傳輸 HTML、CSS、JavaScript
等資源文件。GZip
所需的HTTP
最低版本默認值爲HTTP/1.1
gzip
同時須要客戶端和服務端的支持,若是客戶端支持gzip
的解析,那麼只要服務端可以返回gzip
的文件就能夠啓用gzip
了,咱們能夠經過nginx
的配置來讓服務端支持gzip。下面的respone
中content-encoding:gzip
,指服務端開啓了gzip
的壓縮方式。對於文本文件,GZip 的效果很是明顯,開啓後傳輸所需流量大約會降至 1/4 ~ 1/3。nginx
Nginx
功能很是強大,配置也很是方便,有興趣的能夠多看看這篇文章
Nginx解析
html
文件DOM
樹css
標記,調用css解析器將其解析CSSOM
樹link
阻塞 - 爲了解決閃屏,全部解決閃屏的樣式style
非阻塞,與閃屏的樣式不相關的DOM
樹和CSSOM
樹結合在一塊兒,造成render
樹script
標籤,阻塞,調用js
解析器解析js
代碼,可能會修改DOM
樹,也可能會修改CSSOM
樹DOM
樹和CSSOM
樹結合在一塊兒,造成render
樹layout
佈局 render
渲染(重排重繪)script
標籤的屬性 asnyc defer
性能優化策略:
link
引入,不須要的使用style
標籤(具體是否須要阻塞看業務場景)webpack4
中也要配置圖片壓縮,能極大壓縮圖片大小,對於新版本瀏覽器可使用webp格式圖片
webP詳解,圖片優化對性能提高最大。webpack4
配置 代碼分割,提取公共代碼成單獨模塊。方便緩存/* runtimeChunk 設置爲 true, webpack 就會把 chunk 文件名所有存到一個單獨的 chunk 中, 這樣更新一個文件只會影響到它所在的 chunk 和 runtimeChunk,避免了引用這個 chunk 的文件也發生改變。 */ runtimeChunk: true, splitChunks: { chunks: 'all' // 默認 entry 的 chunk 不會被拆分, 配置成 all, 就能夠了 } } //由於是單入口文件配置,因此沒有考慮多入口的狀況,多入口是應該分別進行處理。
webpack4
配置懶加載的,能夠看這篇webpack4優化教程,寫得很是全面javaScript
的DOM
操做等優化會在下面總結TCP
的四次揮手,斷開鏈接RAIL
es6
Responce
響應,研究代表,100ms內對用戶的輸入操做進行響應,一般會被人類認爲是當即響應。時間再長,操做與反應之間的鏈接就會中斷,人們就會以爲它的操做有延遲。例如:當用戶點擊一個按鈕,若是100ms內給出響應,那麼用戶就會以爲響應很及時,不會察覺到絲毫延遲感。Animaton
現現在大多數設備的屏幕刷新頻率是60Hz,也就是每秒鐘屏幕刷新60次;所以網頁動畫的運行速度只要達到60FPS,咱們就會以爲動畫很流暢。Idle
RAIL規定,空閒週期內運行的任務不得超過50ms,固然不止RAIL規定,W3C性能工做組的Longtasks標準也規定了超過50毫秒的任務屬於長任務,那麼50ms這個數字是怎麼得來的呢?瀏覽器是單線程的,這意味着同一時間主線程只能處理一個任務,若是一個任務執行時間過長,瀏覽器則沒法執行其餘任務,用戶會感受到瀏覽器被卡死了,由於他的輸入得不到任何響應。爲了達到100ms內給出響應,將空閒週期執行的任務限制爲50ms意味着,即便用戶的輸入行爲發生在空閒任務剛開始執行,瀏覽器仍有剩餘的50ms時間用來響應用戶輸入,而不會產生用戶可察覺的延遲。Load
若是不能在1秒鐘內加載網頁並讓用戶看到內容,用戶的注意力就會分散。用戶會以爲他要作的事情被打斷,若是10秒鐘還打不開網頁,用戶會感到失望,會放棄他們想作的事,之後他們或許都不會再回來。如何使網頁更絲滑?
使用requestAnimationFrameweb
避免FSL
面試
先執行JS
,而後在JS
中修改了樣式從而致使樣式計算,而後樣式的改動觸發了佈局、繪製、合成。但JavaScript
能夠強制瀏覽器將佈局提早執行,這就叫 強制同步佈局FSL
。
//讀取offsetWidth的值會致使重繪 const newWidth = container.offsetWidth; //設置width的值會致使重排,可是for循環內部 代碼執行速度極快,當上面的查詢操做致使的重繪 尚未完成,下面的代碼又會致使重排,並且這個重 排會強制結束上面的重繪,直接重排,這樣對性能影響 很是大。因此咱們通常會在循環外部定義一個變量,這裏 面使用變量代替container.offsetWidth; boxes[i].style.width = newWidth + 'px'; }
transform
屬性去操做動畫,這個屬性是由合成器單獨處理的,因此使用這個屬性能夠避免佈局與繪製。translateZ(0)
開啓圖層,減小重繪重排。特別在移動端,儘可能使用transform
代替absolute
。建立圖層的最佳方式是使用will-change,但某些不支持這個屬性的瀏覽器可使用3D 變形(transform: translateZ(0))來強制建立一個新層。class
,經過class
的切換批量修改樣式,避免屢次重繪重排display:none
再修改樣式append
操做能夠先插入到一個新生成的元素中,再一次性插入到頁面中。React
中封裝成高階組件,ES6
中可使用繼承,TypeScript
中接口繼承,類繼承,接口合併,類合併。localstorage和sessionstorage
中時,能夠再本身定義一個模塊,把這些數據在內存中存儲一份,這樣只要能夠直接從內存中讀書,速度更快,性能更好。
下面加入
React
的性能優化方案:
shouldComponentUpdate
中對this.state
和prev state
進行淺比較,使用for-in
循環遍歷二者,只要獲得他們每一項值,只要有一個不同就返回true
,更新組件。
React
機制會自動在shouldComponentUpdate
中進行淺比較,決定是否更新。this.props
和prevprops
的遍歷比較,由於shouldComponentUpdate
的生命週期函數自帶這兩個參數。若是props 和 state
的值比較複雜,那麼可使用下面這種方式去進行深比較。解決:
使用 immutable-js 庫,這個庫保證生成的值都是惟一的
var map1 = Immutable.Map({ a: 1, b: 2, c: 3 }); var map2 = map1.set('b', 50); map1.get('b'); // 2 map2.get('b'); // 50
React
的JSX
語法要求必須包裹一層根標籤,爲了減小沒必要要的DOM
層級,咱們使用Fragment
標籤代替,這樣渲染時候不會渲染多餘的DOM
節點,讓DIFF
算法更快遍歷。Redux
管理全局多個組件複用的狀態。React
構建的是SPA
應用,對SEO
不夠友好,能夠選擇部分SSR
技術進行SEO
優化。Ant-design
這類的UI
組件庫,進行按需加載配置,從import Button from 'antd' 的引入方式,變成import {Button} from antd
的方式引入。(相似Babel7中的runtime和polifill的區別
).React
中一些數據的須要更新,可是卻不急着使用,或者說每次更新的這個數據不須要更新組件從新渲染的,能夠按期成類的實例上的屬性,這樣能減小屢次的重複無心義的DIFF
和渲染。Redux
的使用要看狀況使用,若是隻是一個局部狀態(僅僅是一個組件或者父子組件使用就不要使用Redux
)。對於一個父子、父子孫多層組件須要用到的state數據
,也可使用context上下文
去傳遞. Context上下文詳解,可是複雜項目的多個不一樣層次組件使用到的state
,必須上Redux
。componentWillUnmount
中清除,不然大型項目一定會發生內存泄露,極度影響性能!!!React Hooks是什麼?
用來定義有狀態和生命週期函數的純函數組件(在過去純函數組件是沒有狀態和生命週期函數的~)
Hooks是React v16.7.0-alpha中加入的新特性,並向後兼容。
Hook
)本質就是函數,能讓你使用React組件的狀態和生命週期函數HOC
(高階組件)和class組件
。使用:
useState(initValue) - const [ state, setState ] = React.useState(initValue); - 用來定義狀態數據和操做狀態數據的方法 useEffect(function) - useEffect(() => { do something }) - 反作用函數(發請求獲取數據、訂閱事件、修改DOM等) - 本質上就是一個生命週期函數,至關於componentDidMount 、 componentDidUpdate 和 componentWillUnmount useContext(Context) - context指的是React.createContext返回值 ------ 如下Hooks只使用於特殊場景,須要時在用 ----- useReducer - const [state, dispatch] = useReducer(reducer, initialState); - 一個 useState 替代方案,至關於redux useCallback - useCallback(fn, inputs) - 至關於 shouldComponentUpdate,只有inputs的值發生變化纔會調用fn useMemo(create, inputs) - 至關於useCallback
更多詳見官方文檔:HOOKS文檔
注意
原生
JavaScript
實現懶加載:
img
元素的src
屬性賦值時,不會發出請求【不能使src=""
,這樣即便只給src
賦了空值也會發出請求】,而一旦給src
屬性賦予資源地址值,那麼該請求發出,使得圖片顯示;因此這裏咱們利用這一點控制img
元素的加載時機。在開始的時候將資源url
放置在自定義屬性data-src
當中,而後在須要加載的時候獲取該屬性並賦值給元素的src
屬性el.getBoundingClientRect()
來獲取其位置,並判斷是否在視窗內,這裏簡單描述。Element.getBoundingClientRect()
方法返回元素的大小及其相對於視口的位置。返回值是一個 DOMRect
對象,這個對象是由該元素的 getClientRects()
方法返回的一組矩形的集合, 即:是與該元素相關的CSS 邊框集合 。DOMRect
對象包含了一組用於描述邊框的只讀屬性——left、top、right和bottom
,單位爲像素。除了 width 和 height
外的屬性都是相對於視口的左上角位置而言的。
function isInSight(el){ var eldom = typeof el == 'object'?el:document.querySelector(el); var bound = eldom.getBoundingClientRect(); // 這裏的bound包含了el距離視窗的距離; // bound.left是元素距離窗口左側的距離值; // bound.top是袁術距離窗口頂端的距離值; // 以以上兩個數值判斷元素是否進入視窗; var clientHeigt = window.innerHeight; var clientWidth = window.innerWidth; // return (bound.top>=0&&bound.left>=0)&&(bound.top<=window.innerHeight+20)&&(bound.left<=window.innerWidth+20); return !((bound.top>clientHeigt)||(bound.bottom<0)||(bound.left>clientWidth)||(bound.right<0)) }
window.innerHeight和window.innerWidth
分別爲視窗的高度和寬度,之因此加上20是爲了讓懶加載稍稍提早,使用戶體驗更好;添加scroll事件監聽:
// 當加載完成,檢測並加載可視範圍內的圖片 window.onload= checkAllImgs; // 添加滾動監聽,便可視範圍變化時檢測當前範圍內的圖片是否能夠加載了 window.addEventListener("scroll",function(){ checkAllImgs(); }) // 檢測全部圖片,並給視窗中的圖片的src屬性賦值,即開始加載; function checkAllImgs(){ var imgs = document.querySelectorAll("img"); Array.prototype.forEach.call(imgs,function(el){ if(isInSight(el)){ loadImg(el); } }) } // 開始加載指定el的資源 function loadImg(el){ var eldom = typeof el == 'object'?el:document.querySelector(el); if(!eldom.src){ // 懶加載img定義如:<div class="img"><img alt="加載中" data-index=7 data-src="http://az608707.vo.msecnd.net/files/MartapuraMarket_EN-US9502204987_1366x768.jpg"></div> var source = eldom.getAttribute("data-src"); var index = eldom.getAttribute("data-index"); eldom.src = source; console.log("第"+index+"張圖片進入視窗,開始加載。。。。") } }
如今最新版本的谷歌瀏覽器也要支持<img>
標籤的內部loading
屬性了,相信將來開發會愈來愈方便。 以上都是根據本人的知識點總結得出,後期還會有更多性能優化方案等出來,路過點個贊收藏收藏~,歡迎提出問題補充~