<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          8000字總結(jié)的前端性能優(yōu)化

          共 12964字,需瀏覽 26分鐘

           ·

          2021-03-18 09:56

          性能優(yōu)化是一門大學問,本文僅對個人一些積累知識的闡述,歡迎下面補充。

          拋出一個問題,從輸入url地址欄到所有內(nèi)容顯示到界面上做了哪些事?

          • 1.瀏覽器向DNS服務(wù)器請求解析該 URL 中的域名所對應(yīng)的 IP 地址;
          • 2.建立TCP連接(三次握手);
          • 3.瀏覽器發(fā)出讀取文件(URL 中域名后面部分對應(yīng)的文件)的HTTP 請求,該請求報文作為 TCP 三次握手的第三個報文的數(shù)據(jù)發(fā)送給服務(wù)器;
          • 4.服務(wù)器對瀏覽器請求作出響應(yīng),并把對應(yīng)的 html 文本發(fā)送給瀏覽器;
          • 5.瀏覽器將該 html 文本并顯示內(nèi)容;
          • 6.釋放 TCP連接(四次揮手);

          上面這個問題是一個面試官非常喜歡問的問題,我們下面把這6個步驟分解,逐步細談優(yōu)化。

          一、DNS 解析

          • DNS`解析:將域名解析為ip地址 ,由上往下匹配,只要命中便停止

            - 走緩存
            - 瀏覽器DNS緩存
            - 本機DNS緩存
            - 路由器DNS緩存
            - 網(wǎng)絡(luò)運營商服務(wù)器DNS緩存 (80%的DNS解析在這完成的)
            - 遞歸查詢

          優(yōu)化策略:盡量允許使用瀏覽器的緩存,能給我們節(jié)省大量時間,下面有dns-prefetch的介紹,每次dns解析大概需要20-120秒

          二、TCP的三次握手

          • SYN (同步序列編號)ACK(確認字符)

          • 第一次握手:Client將標志位SYN置為1,隨機產(chǎn)生一個值seq=J,并將該數(shù)據(jù)包發(fā)送給Server,Client進入SYN_SENT狀態(tài),等 待Server確認。

          • 第二次握手:Server收到數(shù)據(jù)包后由標志位SYN=1知道Client請求建立連接,Server將標志位SYN和ACK都置為1,ack=J+1,隨機產(chǎn)生一個值seq=K,并將該數(shù)據(jù)包發(fā)送給Client以確認連接請求,Server進入SYN_RCVD狀態(tài)。

          • 第三次握手:Client收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,并將該數(shù)據(jù)包發(fā)送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態(tài),完成三次握手,隨后Client與Server之間可以開始傳輸數(shù)據(jù)了。

          三、瀏覽器發(fā)送請求

          優(yōu)化策略:

          • 1.HTTP協(xié)議通信最耗費時間的是建立TCP連接的過程,那我們就可以使用HTTP Keep-Alive,在HTTP早期,每個HTTP請求都要求打開一個TCP socket連接,并且使用一次之后就斷開這個TCP連接。 使用keep-alive可以改善這種狀態(tài),即在一次TCP連接中可以持續(xù)發(fā)送多份數(shù)據(jù)而不會斷開連接。通過使用keep-alive機制,可以減少TCP連接建立次數(shù),也意味著可以減少TIME_WAIT狀態(tài)連接,以此提高性能和提高http服務(wù)器的吞吐率(更少的tcp連接意味著更少的系統(tǒng)內(nèi)核調(diào)用
          • 2.但是,keep-alive并不是免費的午餐,長時間的TCP連接容易導致系統(tǒng)資源無效占用。配置不當?shù)?/span>keep-alive,有時比重復利用連接帶來的損失還更大。所以,正確地設(shè)置keep-alive timeout時間非常重要。(這個keep-alive_timout時間值意味著:一個http產(chǎn)生的tcp連接在傳送完最后一個響應(yīng)后,還需要holdkeepalive_timeout秒后,才開始關(guān)閉這個連接),如果想更詳細了解可以看這篇文章[keep-alve性能優(yōu)化的測試結(jié)果][1]
          • 3.使用webScoket通信協(xié)議,僅一次TCP握手就一直保持連接,而且他對二進制數(shù)據(jù)的傳輸有更好的支持,可以應(yīng)用于即時通信,海量高并發(fā)場景。[webSocket的原理以及詳解][2]
          • 4.減少HTTP請求次數(shù),每次HTTP請求都會有請求頭,返回響應(yīng)都會有響應(yīng)頭,多次請求不僅浪費時間而且會讓網(wǎng)絡(luò)傳輸很多無效的資源,使用前端模塊化技術(shù) AMD CMD commonJS ES6等模塊化方案將多個文件壓縮打包成一個,當然也不能都放在一個文件中,因為這樣傳輸起來可能會很慢,權(quán)衡取一個中間值
          • 5.配置使用懶加載,對于一些用戶不立刻使用到的文件到特定的事件觸發(fā)再請求,也許用戶只是想看到你首頁上半屏的內(nèi)容,但是你卻請求了整個頁面的所有圖片,如果用戶量很大,那么這是一種極大的浪費
          • 6.服務(wù)器資源的部署盡量使用同源策略
          • 7.在需要多個cookie去辨識用戶的多種狀況時,使用session替代,把數(shù)據(jù)儲存在服務(wù)器端或者服務(wù)器端的數(shù)據(jù)庫中,這樣只需要一個cookie傳輸,節(jié)省大量的無效傳輸,而且儲存的數(shù)據(jù)可以是永久無線大的。
          • 8.使用preloaddns-prefetchprefetch,預請求資源,這種請求方式不會阻塞瀏覽器的解析,而且能將預請求的資源緩存起來,而且可以設(shè)置crossorgin進行跨域資源的緩存,不會推遲首屏的渲染時間,還會加快后面的加載時間,因為后面的本身需要的資源會直接從緩存中讀取,而不會走網(wǎng)絡(luò)請求。
          • 9.使用deferasync屬性的腳本,異步加載的方式,會先發(fā)請求,然后JS引擎繼續(xù)解析下面的內(nèi)容。async的屬性腳本會無序加載,誰先請求回來就立刻加載誰,當請求回來的時候,無論是在DOM解析還是腳本的解析,接下來都先會解析這個asncy腳本,它會阻塞DOM的解析。defer屬性的會按HTML結(jié)構(gòu)的按順序加載,在DOMContentLoad前加載,但是加載之前所有的DOM解析肯定已經(jīng)完成了,defer屬性的腳本不會阻塞DOM的解析,它也叫延遲腳本。由于實際中它不確定是否在DOMContentLoaded前加載,所以一般只放一個defer的腳本,參考移動端京東網(wǎng)頁。 [async和defer詳解][3]
          • 詳情參考[preload和prefetch詳解][4]

          四、服務(wù)器返回響應(yīng),瀏覽器接受到響應(yīng)數(shù)據(jù)

          一直沒想到這里使用什么優(yōu)化手段,今晚想到了,使用Nginx反向代理服務(wù)器,主要是對服務(wù)器端的優(yōu)化。

          • Nginx是一款輕量級的Web 服務(wù)器/反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器,并在一個BSD-like 協(xié)議下發(fā)行。其特點是占有內(nèi)存少,并發(fā)能力強,事實上nginx的并發(fā)能力確實在同類型的網(wǎng)頁服務(wù)器中表現(xiàn)較好,中國大陸使用nginx網(wǎng)站用戶有:百度、京東、新浪、網(wǎng)易、騰訊、淘寶等。
          • Nginx 是一個安裝非常的簡單、配置文件非常簡潔(還能夠支持perl語法)、Bug非常少的服務(wù)。Nginx 啟動特別容易,并且?guī)缀蹩梢宰龅?*24不間斷運行,即使運行數(shù)個月也不需要重新啟動。你還能夠不間斷服務(wù)的情況下進行軟件版本的升級。
          • 它可以:解決跨域,請求過濾,配置gzip,負載均衡,靜態(tài)資源服務(wù)器 等...
          • 把服務(wù)窗口想像成我們的后端服務(wù)器,而后面終端的人則是無數(shù)個客戶端正在發(fā)起請求。負載均衡就是用來幫助我們將眾多的客戶端請求合理的分配到各個服務(wù)器,以達到服務(wù)端資源的充分利用和更少的請求時間。
          • Nginx如何實現(xiàn)負載均衡
          • nginx如何實現(xiàn)負載均衡
              Upstream指定后端服務(wù)器地址列表
          upstream balanceServer {
          server 10.1.22.33:12345;
          server 10.1.22.34:12345;
          server 10.1.22.35:12345;
          }
          復制代碼在server中攔截響應(yīng)請求,并將請求轉(zhuǎn)發(fā)到Upstream中配置的服務(wù)器列表。
          server {
          server_name fe.server.com;
          listen 80;
          location /api {
          proxy_pass http://balanceServer;
          }
          }
          • 上面的配置只是指定了nginx需要轉(zhuǎn)發(fā)的服務(wù)端列表,并沒有指定分配策略。

          • 默認情況下采用的策略,將所有客戶端請求輪詢分配給服務(wù)端。這種策略是可以正常工作的,但是如果其中某一臺服務(wù)器壓力太大,出現(xiàn)延遲,會影響所有分配在這臺服務(wù)器下的用戶。

          • 最小連接數(shù)策略 將請求優(yōu)先分配給壓力較小的服務(wù)器,它可以平衡每個隊列的長度,并避免向壓力大的服務(wù)器添加更多的請求。

              upstream balanceServer {
          least_conn; //配置壓力較小的服務(wù)器
          server 10.1.22.33:12345;
          server 10.1.22.34:12345;
          server 10.1.22.35:12345;
          }
          • 依賴于NGINX Plus,優(yōu)先分配給響應(yīng)時間最短的服務(wù)器。
          upstream balanceServer {
          fair; //配置響應(yīng)時間最短的服務(wù)器
          server 10.1.22.33:12345;
          server 10.1.22.34:12345;
          server 10.1.22.35:12345;
          }
          • 客戶端ip綁定
          來自同一個ip的請求永遠只分配一臺服務(wù)器,有效解決了動態(tài)網(wǎng)頁存在的session共享問題。
          upstream balanceServer {
          ip_hash; //配置1個IP永遠只分配一臺服務(wù)器
          server 10.1.22.33:12345;
          server 10.1.22.34:12345;
          server 10.1.22.35:12345;
          }
          • 配置靜態(tài)資源服務(wù)器
          location ~* \.(png|gif|jpg|jpeg)$ {
          root /root/static/;
          autoindex on;
          access_log off;
          expires 10h;# 設(shè)置過期時間為10小時
          }
          復制代碼匹配以png|gif|jpg|jpeg為結(jié)尾的請求,
          并將請求轉(zhuǎn)發(fā)到本地路徑,root中指定的路徑即nginx
          本地路徑。同時也可以進行一些緩存的設(shè)置。
          • Nginx解決跨域
          nginx解決跨域的原理
          例如:

          前端server的域名為:fe.server.com
          后端服務(wù)的域名為:dev.server.com

          現(xiàn)在我在fe.server.com對dev.server.com發(fā)起請求一定會出現(xiàn)跨域。
          現(xiàn)在我們只需要啟動一個nginx服務(wù)器,將server_name設(shè)置為fe.server.com,
          然后設(shè)置相應(yīng)的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對服務(wù)端轉(zhuǎn)發(fā)的請求不會觸發(fā)瀏覽器的同源策略。

          • 最重要的一點來了,現(xiàn)在的BATJ大都使用了這種配置:

          • 配置GZIP

          • GZIP是規(guī)定的三種標準HTTP壓縮格式之一。目前絕大多數(shù)的網(wǎng)站都在使用GZIP傳輸 HTML、CSS、JavaScript 等資源文件。

          • 對于文本文件,GZip 的效果非常明顯,開啟后傳輸所需流量大約會降至 1/4 ~ 1/3。

          • 啟用 GZip所需的HTTP 最低版本默認值為HTTP/1.1

          • 啟用gzip同時需要客戶端和服務(wù)端的支持,如果客戶端支持gzip的解析,那么只要服務(wù)端能夠返回gzip的文件就可以啟用gzip了,我們可以通過nginx的配置來讓服務(wù)端支持gzip。下面的responecontent-encoding:gzip,指服務(wù)端開啟了gzip的壓縮方式。

          • 具體可以看這篇文字文章 [Nginx配置GZIP][5]

          對于文本文件,GZip 的效果非常明顯,開啟后傳輸所需流量大約會降至 1/4 ~ 1/3。

          Nginx功能非常強大,配置也非常方便,有興趣的可以多看看這篇文章 [Nginx解析][6]

          五、瀏覽器解析數(shù)據(jù),繪制渲染頁面的過程

          • 先預解析(將需要發(fā)送請求的標簽的請求發(fā)出去)
          • 從上到下解析html文件
          • 遇到HTML標簽,調(diào)用html解析器將其解析DOM
          • 遇到css標記,調(diào)用css解析器將其解析CSSOM
          • link 阻塞 - 為了解決閃屏,所有解決閃屏的樣式
          • style 非阻塞,與閃屏的樣式不相關(guān)的
          • DOM樹和CSSOM樹結(jié)合在一起,形成render
          • layout布局 render渲染
          • 遇到script標簽,阻塞,調(diào)用js解析器解析js代碼,可能會修改DOM樹,也可能會修改CSSOM
          • DOM樹和CSSOM樹結(jié)合在一起,形成render
          • layout布局 render渲染(重排重繪)
          • script標簽的屬性 asnyc defer

          性能優(yōu)化策略:

          • 需要阻塞的樣式使用link引入,不需要的使用style標簽(具體是否需要阻塞看業(yè)務(wù)場景)
          • 圖片比較多的時候,一定要使用懶加載,圖片是最需要優(yōu)化的,webpack4中也要配置圖片壓縮,能極大壓縮圖片大小,對于新版本瀏覽器可以使用webp格式圖片[webP詳解][7],圖片優(yōu)化對性能提升最大。
          • webpack4配置 代碼分割,提取公共代碼成單獨模塊。方便緩存
              /*
          runtimeChunk 設(shè)置為 true, webpack 就會把 chunk 文件名全部存到一個單獨的 chunk 中,
          這樣更新一個文件只會影響到它所在的 chunk 和 runtimeChunk,避免了引用這個 chunk 的文件也發(fā)生改變。
          */
          runtimeChunk: true,
          splitChunks: {
          chunks: 'all' // 默認 entry 的 chunk 不會被拆分, 配置成 all, 就可以了
          }
          }
          //因為是單入口文件配置,所以沒有考慮多入口的情況,多入口是應(yīng)該分別進行處理。
          • 對于需要事件驅(qū)動的webpack4配置懶加載的,可以看這篇[webpack4優(yōu)化教程][8],寫得非常全面

          • 一些原生javaScriptDOM操作等優(yōu)化會在下面總結(jié)

          六、TCP的四次揮手,斷開連接


          終結(jié)篇:性能只是 load 時間或者 DOMContentLoaded 時間的問題嗎?

          • RAIL
          • Responce 響應(yīng),研究表明,100ms內(nèi)對用戶的輸入操作進行響應(yīng),通常會被人類認為是立即響應(yīng)。時間再長,操作與反應(yīng)之間的連接就會中斷,人們就會覺得它的操作有延遲。例如:當用戶點擊一個按鈕,如果100ms內(nèi)給出響應(yīng),那么用戶就會覺得響應(yīng)很及時,不會察覺到絲毫延遲感。
          • Animaton 現(xiàn)如今大多數(shù)設(shè)備的屏幕刷新頻率是60Hz,也就是每秒鐘屏幕刷新60次;因此網(wǎng)頁動畫的運行速度只要達到60FPS,我們就會覺得動畫很流暢。
          • Idle RAIL規(guī)定,空閑周期內(nèi)運行的任務(wù)不得超過50ms,當然不止RAIL規(guī)定,W3C性能工作組的Longtasks標準也規(guī)定了超過50毫秒的任務(wù)屬于長任務(wù),那么50ms這個數(shù)字是怎么得來的呢?瀏覽器是單線程的,這意味著同一時間主線程只能處理一個任務(wù),如果一個任務(wù)執(zhí)行時間過長,瀏覽器則無法執(zhí)行其他任務(wù),用戶會感覺到瀏覽器被卡死了,因為他的輸入得不到任何響應(yīng)。為了達到100ms內(nèi)給出響應(yīng),將空閑周期執(zhí)行的任務(wù)限制為50ms意味著,即使用戶的輸入行為發(fā)生在空閑任務(wù)剛開始執(zhí)行,瀏覽器仍有剩余的50ms時間用來響應(yīng)用戶輸入,而不會產(chǎn)生用戶可察覺的延遲。
          • Load如果不能在1秒鐘內(nèi)加載網(wǎng)頁并讓用戶看到內(nèi)容,用戶的注意力就會分散。用戶會覺得他要做的事情被打斷,如果10秒鐘還打不開網(wǎng)頁,用戶會感到失望,會放棄他們想做的事,以后他們或許都不會再回來。

          如何使網(wǎng)頁更絲滑?

          • 使用requestAnimationFrame

            • 即便你能保證每一幀的總耗時都小于16ms,也無法保證一定不會出現(xiàn)丟幀的情況,這取決于觸發(fā)JS執(zhí)行的方式。假設(shè)使用 setTimeout 或 setInterval 來觸發(fā)JS執(zhí)行并修改樣式從而導致視覺變化;那么會有這樣一種情況,因為setTimeout 或 setInterval沒有辦法保證回調(diào)函數(shù)什么時候執(zhí)行,它可能在每一幀的中間執(zhí)行,也可能在每一幀的最后執(zhí)行。所以會導致即便我們能保障每一幀的總耗時小于16ms,但是執(zhí)行的時機如果在每一幀的中間或最后,最后的結(jié)果依然是沒有辦法每隔16ms讓屏幕產(chǎn)生一次變化,也就是說,即便我們能保證每一幀總體時間小于16ms,但如果使用定時器觸發(fā)動畫,那么由于定時器的觸發(fā)時機不確定,所以還是會導致動畫丟幀。現(xiàn)在整個Web只有一個API可以解決這個問題,那就是requestAnimationFrame,它可以保證回調(diào)函數(shù)穩(wěn)定的在每一幀最開始觸發(fā)。
          • 避免FSL

          • 先執(zhí)行JS,然后在JS中修改了樣式從而導致樣式計算,然后樣式的改動觸發(fā)了布局、繪制、合成。但JavaScript可以強制瀏覽器將布局提前執(zhí)行,這就叫 強制同步布局FSL

              //讀取offsetWidth的值會導致重繪
            const newWidth = container.offsetWidth;

            //設(shè)置width的值會導致重排,但是for循環(huán)內(nèi)部
            代碼執(zhí)行速度極快,當上面的查詢操作導致的重繪
            還沒有完成,下面的代碼又會導致重排,而且這個重
            排會強制結(jié)束上面的重繪,直接重排,這樣對性能影響
            非常大。所以我們一般會在循環(huán)外部定義一個變量,這里
            面使用變量代替container.offsetWidth;
            boxes[i].style.width = newWidth + 'px';
            }
          • 使用transform屬性去操作動畫,這個屬性是由合成器單獨處理的,所以使用這個屬性可以避免布局與繪制。

          • 使用translateZ(0)開啟圖層,減少重繪重排。特別在移動端,盡量使用transform代替absolute。創(chuàng)建圖層的最佳方式是使用will-change,但某些不支持這個屬性的瀏覽器可以使用3D 變形(transform: translateZ(0))來強制創(chuàng)建一個新層。

          • 有興趣的可以看看這篇文字 [前端頁面優(yōu)化][9]

          • 樣式的切換最好提前定義好class,通過class的切換批量修改樣式,避免多次重繪重排

          • 可以先切換display:none再修改樣式

          • 多次的append操作可以先插入到一個新生成的元素中,再一次性插入到頁面中。

          • 代碼復用,函數(shù)柯里化,封裝高階函數(shù),將多次復用代碼封裝成普通函數(shù)(俗稱方法),React中封裝成高階組件,ES6中可以使用繼承,TypeScript中接口繼承,類繼承,接口合并,類合并。

          • 在把數(shù)據(jù)儲存在localstorage和sessionstorage中時,可以再自己定義一個模塊,把這些數(shù)據(jù)在內(nèi)存中存儲一份,這樣只要可以直接從內(nèi)存中讀書,速度更快,性能更好。

          • 能不定義全局變量就不定義全局變量,最好使用局部變量代替全局變量,查找的速度要高一倍。

          • 強力推薦閱讀:[阮一峰ES6教程][10]

          • 以及[什么是TypeScript以及入門][11]



          下面加入React的性能優(yōu)化方案:

          • 在生命周期函數(shù)shouldComponentUpdate中對this.stateprev state進行淺比較,使用for-in循環(huán)遍歷兩者, 只要得到他們每一項值,只要有一個不一樣就返回true,更新組件。

          • 定義組件時不適用React.component , 使用PureComponent代替,這樣React機制會自動在shouldComponentUpdate中進行淺比較,決定是否更新。

          • 上面兩條優(yōu)化方案只進行淺比較,只對比直接屬性的值,當然你還可以在上面加入this.propsprevprops的遍歷比較,因為shouldComponentUpdate的生命周期函數(shù)自帶這兩個參數(shù)。如果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
          • 總結(jié):使用以上方式,可以減少不必要的重復渲染。

          • ReactJSX語法要求必須包裹一層根標簽,為了減少不必要的DOM層級,我們使用Fragment標簽代替,這樣渲染時候不會渲染多余的DOM節(jié)點,讓DIFF算法更快遍歷。

          • 使用Redux管理全局多個組件復用的狀態(tài)。

          • React構(gòu)建的是SPA應(yīng)用,對SEO不夠友好,可以選擇部分SSR技術(shù)進行SEO優(yōu)化。

          • Ant-design這類的UI組件庫,進行按需加載配置,從import Button from 'antd' 的引入方式,變成import {Button} from antd的方式引入。(類似Babel7中的runtime和polifill的區(qū)別).

          • React中一些數(shù)據(jù)的需要更新,但是卻不急著使用,或者說每次更新的這個數(shù)據(jù)不需要更新組件重新渲染的,可以定期成類的實例上的屬性,這樣能減少多次的重復無意義的DIFF和渲染。

          • Redux的使用要看情況使用,如果只是一個局部狀態(tài)(僅僅是一個組件或者父子組件使用就不要使用Redux)。對于一個父子、父子孫多層組件需要用到的state數(shù)據(jù),也可以使用context上下文去傳遞. [Context上下文詳解][12],但是復雜項目的多個不同層次組件使用到的state,必須上Redux

          • 所有的原生監(jiān)聽事件,定時器等,必須在componentWillUnmount中清除,否則大型項目必定會發(fā)生內(nèi)存泄露,極度影響性能!!!

          • React Hooks?

          • React Hooks是什么? * 用來定義有狀態(tài)和生命周期函數(shù)的純函數(shù)組件(在過去純函數(shù)組件是沒有狀態(tài)和生命周期函數(shù)的~) Hooks是React v16.7.0-alpha中加入的新特性,并向后兼容。 * 什么是鉤子(Hook)本質(zhì)就是函數(shù),能讓你使用React組件的狀態(tài)和生命周期函數(shù) * 讓代碼更加可復用,不用在定義繁雜的HOC(高階組件)和class組件。 * 使用:

            ```
            useState(initValue)
            - const [ state, setState ] = React.useState(initValue);
            - 用來定義狀態(tài)數(shù)據(jù)和操作狀態(tài)數(shù)據(jù)的方法
            useEffect(function)
            - useEffect(() => { do something })
            - 副作用函數(shù)(發(fā)請求獲取數(shù)據(jù)、訂閱事件、修改DOM等)
            - 本質(zhì)上就是一個生命周期函數(shù),相當于componentDidMount 、 componentDidUpdate 和 componentWillUnmount
            useContext(Context)
            - context指的是React.createContext返回值

            ------ 以下Hooks只使用于特殊場景,需要時在用 -----
            useReducer
            - const [state, dispatch] = useReducer(reducer, initialState);
            - 一個 useState 替代方案,相當于redux
            useCallback
            - useCallback(fn, inputs)
            - 相當于 shouldComponentUpdate,只有inputs的值發(fā)生變化才會調(diào)用fn
            useMemo(create, inputs)
            - 相當于useCallback
            ```

          • 更多詳見官方文檔:[HOOKS文檔][13] 注意
            • 只能在頂層調(diào)用鉤子。不要在循環(huán),控制流和嵌套的函數(shù)中調(diào)用鉤子。
            • 只能從React的函數(shù)式組件中調(diào)用鉤子。不要在常規(guī)的JavaScript函數(shù)中調(diào)用鉤子。-(此外,你也可以在你的自定義鉤子中調(diào)用鉤子。)

          原生JavaScript實現(xiàn)懶加載:

          • 懶加載,從字面意思就可以簡單的理解為不到用時就不去加載,對于頁面中的元素,我們可以這樣理解:只有當滾動頁面內(nèi)容使得本元素進入到瀏覽器視窗時(或者稍微提前,需給定提前量),我們才開始加載圖片;
          • 不給img元素的src屬性賦值時,不會發(fā)出請求【不能使src="",這樣即使只給src賦了空值也會發(fā)出請求】,而一旦給src屬性賦予資源地址值,那么該請求發(fā)出,使得圖片顯示;所以這里我們利用這一點控制img元素的加載時機。在開始的時候?qū)①Y源url放置在自定義屬性src當中,然后在需要加載的時候獲取該屬性并賦值給元素的src屬性
          • 從上面的分析可以看出來,主要要解決的問題就是怎么檢測到元素是否在視窗當中,這里我們要借助于dom操作api當中的el.getBoundingClientRect()來獲取其位置,并判斷是否在視窗內(nèi),這里簡單描述。
          • Element.getBoundingClientRect()方法返回元素的大小及其相對于視口的位置。返回值是一個 DOMRect對象,這個對象是由該元素的 getClientRects()方法返回的一組矩形的集合, 即:是與該元素相關(guān)的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是元素距離窗口左側(cè)的距離值;
          // bound.top是袁術(shù)距離窗口頂端的距離值;

          // 以以上兩個數(shù)值判斷元素是否進入視窗;
          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事件監(jiān)聽:

          • 那么什么時候去檢測元素是否在視窗內(nèi),并判斷是否加載呢,這里由于頁面的滾動會使得元素相對于視窗的位置發(fā)生變化,也就是說滾動會改變isInSight的結(jié)果,所以這里我們在window上添加scroll事件監(jiān)聽:

           // 當加載完成,檢測并加載可視范圍內(nèi)的圖片
          window.onload= checkAllImgs;
          // 添加滾動監(jiān)聽,即可視范圍變化時檢測當前范圍內(nèi)的圖片是否可以加載了
          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 src="http://az608707.vo.msecnd.net/files/MartapuraMarket_EN-US9502204987_1366x768.jpg"></div>
          var source = eldom.getAttribute("src");
          var index = eldom.getAttribute("data-index");
          eldom.src = source;
          console.log("第"+index+"張圖片進入視窗,開始加載。。。。")
          }

          }
          • 這樣就實現(xiàn)了圖片的懶加載的簡單實現(xiàn),當然還可以對scroll進行優(yōu)化等操作。

          • 現(xiàn)在最新版本的谷歌瀏覽器也要支持 <img>標簽的內(nèi)部 loading屬性了,相信未來開發(fā)會越來越方便。

          點個在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月天色欲 | 日韩无码影音先锋 | 色情一级A片成人片 | 日韩一级片免费看 | 美女嗦鸡吧视频网站 |