拋出一個問題,從輸入
url
地址欄到全部內容顯示到界面上作了哪些事?css
DNS
服務器請求解析該 URL 中的域名所對應的 IP
地址;TCP
鏈接(三次握手);URL
中域名後面部分對應的文件)的HTTP
請求,該請求報文做爲 TCP
三次握手的第三個報文的數據發送給服務器;html
文本並顯示內容;TCP
鏈接(四次揮手);上面這個問題是一個面試官很是喜歡問的問題,咱們下面把這6個步驟分解,逐步細談優化。html
DNS
解析DNS`解析:將域名解析爲ip地址 ,由上往下匹配,只要命中便中止前端
- 走緩存
- 瀏覽器DNS緩存
- 本機DNS緩存
- 路由器DNS緩存
- 網絡運營商服務器DNS緩存 (80%的DNS解析在這完成的)
- 遞歸查詢
複製代碼
優化策略:儘可能容許使用瀏覽器的緩存,能給咱們節省大量時間。java
TCP
的三次握手SYN (同步序列編號)ACK(確認字符)react
第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等 待Server確認。webpack
第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求創建鏈接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認鏈接請求,Server進入SYN_RCVD狀態。nginx
第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,若是正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,若是正確則鏈接創建成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間能夠開始傳輸數據了。es6
優化策略:web
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性能優化的測試結果][1]webScoket
通訊協議,僅一次TCP
握手就一直保持鏈接,並且他對二進制數據的傳輸有更好的支持,能夠應用於即時通訊,海量高併發場景。[webSocket的原理以及詳解][2]HTTP
請求次數,每次HTTP
請求都會有請求頭,返回響應都會有響應頭,屢次請求不只浪費時間並且會讓網絡傳輸不少無效的資源,使用前端模塊化技術 AMD CMD commonJS ES6等模塊化方案
將多個文件壓縮打包成一個,固然也不能都放在一個文件中,由於這樣傳輸起來可能會很慢,權衡取一箇中間值cookie
去辨識用戶的多種情況時,使用session
替代,把數據儲存在服務器端或者服務器端的數據庫中,這樣只須要一個cookie
傳輸,節省大量的無效傳輸,並且儲存的數據能夠是永久無線大的。一直沒想到這裏使用什麼優化手段,今晚想到了,使用
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;
}
}
複製代碼
上面的配置只是指定了nginx須要轉發的服務端列表,並無指定分配策略。
默認狀況下采用的策略,將全部客戶端請求輪詢分配給服務端。這種策略是能夠正常工做的,可是若是其中某一臺服務器壓力太大,出現延遲,會影響全部分配在這臺服務器下的用戶。
最小鏈接數策略 將請求優先分配給壓力較小的服務器,它能夠平衡每一個隊列的長度,並避免向壓力大的服務器添加更多的請求。
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對服務端轉發的請求不會觸發瀏覽器的同源策略。
複製代碼
Nginx
功能很是強大,配置也很是方便,有興趣的能夠多看看這篇文章 [Nginx解析][3]
html
文件DOM
樹css
標記,調用css解析器將其解析CSSOM
樹link
阻塞 - 爲了解決閃屏,全部解決閃屏的樣式style
非阻塞,與閃屏的樣式不相關的DOM
樹和CSSOM
樹結合在一塊兒,造成render
樹script
標籤,阻塞,調用js
解析器解析js
代碼,可能會修改DOM
樹,也可能會修改CSSOM
樹DOM
樹和CSSOM
樹結合在一塊兒,造成render
樹layout
佈局 render
渲染(重排重繪)script
標籤的屬性
性能優化策略:
link
引入,不須要的使用style
標籤(具體是否須要阻塞看業務場景)webpack4
中也要配置圖片壓縮,能極大壓縮圖片大小,對於新版本瀏覽器可使用webp格式圖片
[webP詳解][4],圖片優化對性能提高最大。webpack4
配置 代碼分割,提取公共代碼成單獨模塊。方便緩存/*
runtimeChunk 設置爲 true, webpack 就會把 chunk 文件名所有存到一個單獨的 chunk 中,
這樣更新一個文件只會影響到它所在的 chunk 和 runtimeChunk,避免了引用這個 chunk 的文件也發生改變。
*/
runtimeChunk: true,
splitChunks: {
chunks: 'all' // 默認 entry 的 chunk 不會被拆分, 配置成 all, 就能夠了
}
}
//由於是單入口文件配置,因此沒有考慮多入口的狀況,多入口是應該分別進行處理。
複製代碼
對於須要事件驅動的webpack4
配置懶加載的,能夠看這篇[webpack4優化教程][5],寫得很是全面
一些原生javaScript
的DOM
操做等優化會在下面總結
TCP
的四次揮手,斷開鏈接RAIL
Responce
響應,研究代表,100ms內對用戶的輸入操做進行響應,一般會被人類認爲是當即響應。時間再長,操做與反應之間的鏈接就會中斷,人們就會以爲它的操做有延遲。例如:當用戶點擊一個按鈕,若是100ms內給出響應,那麼用戶就會以爲響應很及時,不會察覺到絲毫延遲感。Animaton
現現在大多數設備的屏幕刷新頻率是60Hz,也就是每秒鐘屏幕刷新60次;所以網頁動畫的運行速度只要達到60FPS,咱們就會以爲動畫很流暢。Idle
RAIL規定,空閒週期內運行的任務不得超過50ms,固然不止RAIL規定,W3C性能工做組的Longtasks標準也規定了超過50毫秒的任務屬於長任務,那麼50ms這個數字是怎麼得來的呢?瀏覽器是單線程的,這意味着同一時間主線程只能處理一個任務,若是一個任務執行時間過長,瀏覽器則沒法執行其餘任務,用戶會感受到瀏覽器被卡死了,由於他的輸入得不到任何響應。爲了達到100ms內給出響應,將空閒週期執行的任務限制爲50ms意味着,即便用戶的輸入行爲發生在空閒任務剛開始執行,瀏覽器仍有剩餘的50ms時間用來響應用戶輸入,而不會產生用戶可察覺的延遲。Load
若是不能在1秒鐘內加載網頁並讓用戶看到內容,用戶的注意力就會分散。用戶會以爲他要作的事情被打斷,若是10秒鐘還打不開網頁,用戶會感到失望,會放棄他們想作的事,之後他們或許都不會再回來。如何使網頁更絲滑?
使用requestAnimationFrame
避免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))來強制建立一個新層。
有興趣的能夠看看這篇文字 [前端頁面優化][6]
樣式的切換最好提早定義好class
,經過class
的切換批量修改樣式,避免屢次重繪重排
能夠先切換display:none
再修改樣式
屢次的append
操做能夠先插入到一個新生成的元素中,再一次性插入到頁面中。
代碼複用,函數柯里化,封裝高階函數,將屢次複用代碼封裝成普通函數(俗稱方法),React
中封裝成高階組件,ES6
中可使用繼承,TypeScript
中接口繼承,類繼承,接口合併,類合併。
在把數據儲存在localstorage和sessionstorage
中時,能夠再本身定義一個模塊,把這些數據在內存中存儲一份,這樣只要能夠直接從內存中讀書,速度更快,性能更好。
能不定義全局變量就不定義全局變量,最好使用局部變量代替全局變量,查找的速度要高一倍。
強力推薦閱讀:[阮一峯ES6教程][7]
以及[什麼是TypeScript以及入門][8]
下面加入
React
的性能優化方案:
在生命週期函數shouldComponentUpdate
中對this.state
和prev state
進行淺比較,使用for-in
循環遍歷二者, 只要獲得他們每一項值,只要有一個不同就返回true
,更新組件。
定義組件時不適用React.component , 使用PureComponent代替,這樣React
機制會自動在shouldComponentUpdate
中進行淺比較,決定是否更新。
上面兩條優化方案只進行淺比較,只對比直接屬性的值,固然你還能夠在上面加入this.props
和prevprops
的遍歷比較,由於shouldComponentUpdate
的生命週期函數自帶這兩個參數。若是props 和 state
的值比較複雜,那麼可使用下面這種方式去進行深比較。
解決:
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上下文詳解][9],可是複雜項目的多個不一樣層次組件使用到的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
```
複製代碼
原生
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事件監聽:
那麼何時去檢測元素是否在視窗內,並判斷是否加載呢,這裏因爲頁面的滾動會使得元素相對於視窗的位置發生變化,也就是說滾動會改變isInSight的結果,因此這裏咱們在window上添加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+"張圖片進入視窗,開始加載。。。。")
}
}
```
* 這樣就實現了圖片的懶加載的簡單實現,固然還能夠對scroll進行優化等操做。
> 如今最新版本的谷歌瀏覽器也要支持 `<img>`標籤的內部 `loading`屬性了,相信將來開發會愈來愈方便。
>以上都是根據本人的知識點總結得出,後期還會有更多性能優化方案等出來,路過點個贊收藏收藏~,歡迎提出問題補充~
[1]: https://www.cnblogs.com/freefish12/p/5394876.html
[2]: https://www.cnblogs.com/fuqiang88/p/5956363.html
[3]: https://juejin.im/post/5c85a64d6fb9a04a0e2e038c#heading-10
[4]: https://baike.baidu.com/item/webp%E6%A0%BC%E5%BC%8F/4077671?fr=aladdin
[5]: https://segmentfault.com/a/1190000018535749
[6]: https://juejin.im/post/5c860282e51d45531330e10e#heading-12
[7]: http://es6.ruanyifeng.com/#docs/promise
[8]: https://ts.xcatliu.com/introduction/what-is-typescript.html
[9]: https://react.docschina.org/docs/context.html
[10]: https://react.docschina.org/docs/hooks-intro.html複製代碼