前端性能優化不徹底手冊

性能優化是一門大學問,本文僅對我的一些積累知識的闡述,歡迎下面補充。

拋出一個問題,從輸入 url地址欄到全部內容顯示到界面上作了哪些事?
  • 1.瀏覽器向 DNS 服務器請求解析該 URL 中的域名所對應的 IP 地址;
  • 2.創建TCP鏈接(三次握手);
  • 3.瀏覽器發出讀取文件(URL 中域名後面部分對應的文件)的HTTP 請求,該請求報文做爲 TCP 三次握手的第三個報文的數據發送給服務器;
  • 4.服務器對瀏覽器請求做出響應,並把對應的 html 文本發送給瀏覽器;
  • 5.瀏覽器將該 html 文本並顯示內容;
  • 6.釋放 TCP鏈接(四次揮手);
上面這個問題是一個面試官很是喜歡問的問題,咱們下面把這6個步驟分解,逐步細談優化。

1、DNS 解析

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

    • 走緩存
    • 瀏覽器DNS緩存
    • 本機DNS緩存
    • 路由器DNS緩存
    • 網絡運營商服務器DNS緩存 (80%的DNS解析在這完成的)
    • 遞歸查詢
優化策略:儘可能容許使用瀏覽器的緩存,能給咱們節省大量時間,下面有 dns-prefetch的介紹,每次 dns解析大概須要 20-120秒

2、TCP的三次握手

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

    • 第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等 待Server確認。
    • 第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求創建鏈接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認鏈接請求,Server進入SYN_RCVD狀態。
    • 第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,若是正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,若是正確則鏈接創建成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間能夠開始傳輸數據了。

3、瀏覽器發送請求

優化策略:
    • 1.HTTP協議通訊最耗費時間的是創建TCP鏈接的過程,那咱們就可使用HTTP Keep-Alive,在HTTP 早期,每一個HTTP 請求都要求打開一個TCP socket鏈接,而且使用一次以後就斷開這個TCP鏈接。 使用keep-alive能夠改善這種狀態,即在一次TCP鏈接中能夠持續發送多份數據而不會斷開鏈接。經過使用keep-alive機制,能夠減小TCP鏈接創建次數,也意味着能夠減小TIME_WAIT狀態鏈接,以此提升性能和提升http服務器的吞吐率(更少的tcp鏈接意味着更少的系統內核調用
    • 2.可是,keep-alive並非免費的午飯,長時間的TCP鏈接容易致使系統資源無效佔用。配置不當的keep-alive,有時比重複利用鏈接帶來的損失還更大。因此,正確地設置keep-alive timeout時間很是重要。(這個keep-alive_timout時間值意味着:一個http產生的tcp鏈接在傳送完最後一個響應後,還須要holdkeepalive_timeout秒後,纔開始關閉這個鏈接),若是想更詳細瞭解能夠看這篇文章keep-alve性能優化的測試結果
    • 3.使用webScoket通訊協議,僅一次TCP握手就一直保持鏈接,並且他對二進制數據的傳輸有更好的支持,能夠應用於即時通訊,海量高併發場景。webSocket的原理以及詳解
    • 4.減小HTTP請求次數,每次HTTP請求都會有請求頭,返回響應都會有響應頭,屢次請求不只浪費時間並且會讓網絡傳輸不少無效的資源,使用前端模塊化技術 AMD CMD commonJS ES6等模塊化方案將多個文件壓縮打包成一個,固然也不能都放在一個文件中,由於這樣傳輸起來可能會很慢,權衡取一箇中間值
    • 5.配置使用懶加載,對於一些用戶不馬上使用到的文件到特定的事件觸發再請求,也許用戶只是想看到你首頁上半屏的內容,可是你卻請求了整個頁面的全部圖片,若是用戶量很大,那麼這是一種極大的浪費
    • 6.服務器資源的部署儘可能使用同源策略
    • 7.在須要多個cookie去辨識用戶的多種情況時,使用session替代,把數據儲存在服務器端或者服務器端的數據庫中,這樣只須要一個cookie傳輸,節省大量的無效傳輸,並且儲存的數據能夠是永久無線大的。
    • 8.使用preloaddns-prefetchprefetch,預請求資源,這種請求方式不會阻塞瀏覽器的解析,並且能將預請求的資源緩存起來,並且能夠設置crossorgin進行跨域資源的緩存,不會推遲首屏的渲染時間,還會加快後面的加載時間,由於後面的自己須要的資源會直接從緩存中讀取,而不會走網絡請求。
    • 9.使用deferasync屬性的腳本,異步加載的方式,會先發請求,而後JS引擎繼續解析下面的內容。async的屬性腳本會無序加載,誰先請求回來就馬上加載誰,當請求回來的時候,不管是在DOM解析仍是腳本的解析,接下來都先會解析這個asncy腳本,它會阻塞DOM的解析。defer屬性的會按HTML結構的按順序加載,在DOMContentLoad前加載,可是加載以前全部的DOM解析確定已經完成了,defer屬性的腳本不會阻塞DOM的解析,它也叫延遲腳本。因爲實際中它不肯定是否在DOMContentLoaded前加載,因此通常只放一個defer的腳本,參考移動端京東網頁async和defer詳解
    • 詳情參考preload和prefetch詳解

    4、服務器返回響應,瀏覽器接受到響應數據

    一直沒想到這裏使用什麼優化手段,今晚想到了,使用 Nginx反向代理服務器,主要是對服務器端的優化。
    • Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,並在一個BSD-like 協議下發行。其特色是佔有內存少,併發能力強,事實上nginx的併發能力確實在同類型的網頁服務器中表現較好,中國大陸使用nginx網站用戶有:百度、京東、新浪、網易、騰訊、淘寶等。
    • Nginx 是一個安裝很是的簡單、配置文件很是簡潔(還可以支持perl語法)、Bug很是少的服務。Nginx 啓動特別容易,而且幾乎能夠作到7*24不間斷運行,即便運行數個月也不須要從新啓動。你還可以不間斷服務的狀況下進行軟件版本的升級。
    • 它能夠:解決跨域,請求過濾,配置gzip,負載均衡,靜態資源服務器 等...
    • 把服務窗口想像成咱們的後端服務器,然後面終端的人則是無數個客戶端正在發起請求。負載均衡就是用來幫助咱們將衆多的客戶端請求合理的分配到各個服務器,以達到服務端資源的充分利用和更少的請求時間。
    • Nginx如何實現負載均衡前端

      • 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須要轉發的服務端列表,並無指定分配策略。
    • 默認狀況下采用的策略,將全部客戶端請求輪詢分配給服務端。這種策略是能夠正常工做的,可是若是其中某一臺服務器壓力太大,出現延遲,會影響全部分配在這臺服務器下的用戶。
    • 最小鏈接數策略

    將請求優先分配給壓力較小的服務器,它能夠平衡每一個隊列的長度,並避免向壓力大的服務器添加更多的請求。java

    upstream balanceServer {
            least_conn; //配置壓力較小的服務器
            server 10.1.22.33:12345;
            server 10.1.22.34:12345;
            server 10.1.22.35:12345;
        }
    • 依賴於NGINX Plus,優先分配給響應時間最短的服務器。
    upstream balanceServer {
        fair; //配置響應時間最短的服務器
        server 10.1.22.33:12345;
        server 10.1.22.34:12345;
        server 10.1.22.35:12345;
    }
    • 客戶端ip綁定
    來自同一個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 的效果很是明顯,開啓後傳輸所需流量大約會降至 1/4 ~ 1/3。
        • 啓用 GZip 所需的HTTP 最低版本默認值爲HTTP/1.1
        • 啓用gzip同時須要客戶端和服務端的支持,若是客戶端支持gzip的解析,那麼只要服務端可以返回gzip的文件就能夠啓用gzip了,咱們能夠經過nginx的配置來讓服務端支持gzip。下面的responecontent-encoding:gzip,指服務端開啓了gzip的壓縮方式。
      • 具體能夠看這篇文字文章 Nginx配置GZIP

    對於文本文件,GZip 的效果很是明顯,開啓後傳輸所需流量大約會降至 1/4 ~ 1/3。nginx

    Nginx功能很是強大,配置也很是方便,有興趣的能夠多看看這篇文章 Nginx解析

    5、瀏覽器解析數據,繪製渲染頁面的過程

    • 先預解析(將須要發送請求的標籤的請求發出去)
    • 從上到下解析html文件
    • 遇到HTML標籤,調用html解析器將其解析DOM
    • 遇到css標記,調用css解析器將其解析CSSOM
    • link 阻塞 - 爲了解決閃屏,全部解決閃屏的樣式
    • style 非阻塞,與閃屏的樣式不相關的
    • DOM樹和CSSOM樹結合在一塊兒,造成render
    • layout佈局 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優化教程,寫得很是全面
    • 一些原生javaScriptDOM操做等優化會在下面總結

    6、TCP的四次揮手,斷開鏈接


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

    • RAILes6

      • Responce 響應,研究代表,100ms內對用戶的輸入操做進行響應,一般會被人類認爲是當即響應。時間再長,操做與反應之間的鏈接就會中斷,人們就會以爲它的操做有延遲。例如:當用戶點擊一個按鈕,若是100ms內給出響應,那麼用戶就會以爲響應很及時,不會察覺到絲毫延遲感。
      • Animaton 現現在大多數設備的屏幕刷新頻率是60Hz,也就是每秒鐘屏幕刷新60次;所以網頁動畫的運行速度只要達到60FPS,咱們就會以爲動畫很流暢。
      • Idle RAIL規定,空閒週期內運行的任務不得超過50ms,固然不止RAIL規定,W3C性能工做組的Longtasks標準也規定了超過50毫秒的任務屬於長任務,那麼50ms這個數字是怎麼得來的呢?瀏覽器是單線程的,這意味着同一時間主線程只能處理一個任務,若是一個任務執行時間過長,瀏覽器則沒法執行其餘任務,用戶會感受到瀏覽器被卡死了,由於他的輸入得不到任何響應。爲了達到100ms內給出響應,將空閒週期執行的任務限制爲50ms意味着,即便用戶的輸入行爲發生在空閒任務剛開始執行,瀏覽器仍有剩餘的50ms時間用來響應用戶輸入,而不會產生用戶可察覺的延遲。
      • Load若是不能在1秒鐘內加載網頁並讓用戶看到內容,用戶的注意力就會分散。用戶會以爲他要作的事情被打斷,若是10秒鐘還打不開網頁,用戶會感到失望,會放棄他們想作的事,之後他們或許都不會再回來。
      如何使網頁更絲滑?
      • 使用requestAnimationFrameweb

        • 即使你能保證每一幀的總耗時都小於16ms,也沒法保證必定不會出現丟幀的狀況,這取決於觸發JS執行的方式。假設使用 setTimeout 或 setInterval 來觸發JS執行並修改樣式從而致使視覺變化;那麼會有這樣一種狀況,由於setTimeout 或 setInterval沒有辦法保證回調函數何時執行,它可能在每一幀的中間執行,也可能在每一幀的最後執行。因此會致使即使咱們能保障每一幀的總耗時小於16ms,可是執行的時機若是在每一幀的中間或最後,最後的結果依然是沒有辦法每隔16ms讓屏幕產生一次變化,也就是說,即使咱們能保證每一幀整體時間小於16ms,但若是使用定時器觸發動畫,那麼因爲定時器的觸發時機不肯定,因此仍是會致使動畫丟幀。如今整個Web只有一個API能夠解決這個問題,那就是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))來強制建立一個新層。
      • 有興趣的能夠看看這篇文字 前端頁面優化
      • 樣式的切換最好提早定義好class,經過class的切換批量修改樣式,避免屢次重繪重排
      • 能夠先切換display:none再修改樣式
      • 屢次的append 操做能夠先插入到一個新生成的元素中,再一次性插入到頁面中。
      • 代碼複用,函數柯里化,封裝高階函數,將屢次複用代碼封裝成普通函數(俗稱方法),React中封裝成高階組件,ES6中可使用繼承,TypeScript中接口繼承,類繼承,接口合併,類合併。
      • 在把數據儲存在localstorage和sessionstorage中時,能夠再本身定義一個模塊,把這些數據在內存中存儲一份,這樣只要能夠直接從內存中讀書,速度更快,性能更好。
      • 能不定義全局變量就不定義全局變量,最好使用局部變量代替全局變量,查找的速度要高一倍。
      • 強力推薦閱讀:阮一峯ES6教程
      • 以及什麼是TypeScript以及入門


    下面加入 React的性能優化方案:
    • 在生命週期函數shouldComponentUpdate中對this.stateprev state進行淺比較,使用for-in循環遍歷二者,

    只要獲得他們每一項值,只要有一個不同就返回true,更新組件。

    • 定義組件時不適用React.component , 使用PureComponent代替,這樣React機制會自動在shouldComponentUpdate中進行淺比較,決定是否更新。
    • 上面兩條優化方案只進行淺比較,只對比直接屬性的值,固然你還能夠在上面加入this.propsprevprops的遍歷比較,由於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
    • 總結:使用以上方式,能夠減小沒必要要的重複渲染。
    • ReactJSX語法要求必須包裹一層根標籤,爲了減小沒必要要的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文檔
      注意

      • 只能在頂層調用鉤子。不要在循環,控制流和嵌套的函數中調用鉤子。
      • 只能從React的函數式組件中調用鉤子。不要在常規的JavaScript函數中調用鉤子。-(此外,你也能夠在你的自定義鉤子中調用鉤子。)
    原生 JavaScript實現懶加載:
    • 懶加載,從字面意思就能夠簡單的理解爲不到用時就不去加載,對於頁面中的元素,咱們能夠這樣理解:只有當滾動頁面內容使得本元素進入到瀏覽器視窗時(或者稍微提早,需給定提早量),咱們纔開始加載圖片;
    • 不給img元素的src屬性賦值時,不會發出請求【不能使src="",這樣即便只給src賦了空值也會發出請求】,而一旦給src屬性賦予資源地址值,那麼該請求發出,使得圖片顯示;因此這裏咱們利用這一點控制img元素的加載時機。在開始的時候將資源url放置在自定義屬性data-src當中,而後在須要加載的時候獲取該屬性並賦值給元素的src屬性
    • 從上面的分析能夠看出來,主要要解決的問題就是怎麼檢測到元素是否在視窗當中,這裏咱們要藉助於dom操做api當中的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屬性了,相信將來開發會愈來愈方便。 以上都是根據本人的知識點總結得出,後期還會有更多性能優化方案等出來,路過點個贊收藏收藏~,歡迎提出問題補充~
    相關文章
    相關標籤/搜索