1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        用 Service Worker 實(shí)現(xiàn)前端性能優(yōu)化

        共 12569字,需瀏覽 26分鐘

         ·

        2021-03-20 13:14

           戳藍(lán)字「前端技術(shù)優(yōu)選」關(guān)注我們哦!

        作者:RetroAstro

        https://github.com/RetroAstro/cosmos-blog

        前言

        說(shuō)起前端性能優(yōu)化, 我們首先想到的可能就是用 Gulp 、Webpack 之類(lèi)的自動(dòng)化構(gòu)建工具對(duì) HTML、CSS 、JS 代碼進(jìn)行壓縮,同時(shí)優(yōu)化圖片資源。再者就是使用 CSS Sprite 或者對(duì)于較小的圖片用 base64 直接編碼來(lái)進(jìn)行優(yōu)化。當(dāng)然還有很多可以優(yōu)化的方向,例如考慮瀏覽器緩存、頁(yè)面渲染性能 ( 減少重排與重繪與 GPU 硬件加速 ) 、JS阻塞性能等等。但我們今天講的是如何利用緩存策略在適宜的情況下直接減少對(duì)前端數(shù)據(jù)的請(qǐng)求量從而達(dá)到前端性能的優(yōu)化。因此 Service Worker 以及其相關(guān)的 API 就成為了我們今天的主角。

        提醒 : 本篇文章將直接講述如何利用 Service Worker 對(duì)前端性能進(jìn)行優(yōu)化,希望讀者在此之前已經(jīng)對(duì) Service Worker 有基本的了解,若之前沒(méi)有接觸過(guò),可以先看看以下的兩篇文章。

        Service Worker ~ Google ( 墻 )

        Service Worker 簡(jiǎn)介

        制定緩存策略

        首先,既然是前端性能優(yōu)化,我們就需要想想該如何制定緩存策略才能達(dá)到理想的效果。我們可能有這樣的想法,即對(duì) CSS 、JS 等易更改文件優(yōu)先使用網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù),而對(duì)于圖片資源則優(yōu)先使用緩存。如果再進(jìn)一步思考的話,我們也許會(huì)希望在網(wǎng)絡(luò)條件好的情況下優(yōu)先使用網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),而網(wǎng)絡(luò)條件較差時(shí)則盡可能的直接使用緩存。嗯 ~ 看起來(lái)還不錯(cuò),那么根據(jù)以上的兩點(diǎn)我們先用代碼來(lái)實(shí)現(xiàn)一下吧。

        先邁出最簡(jiǎn)單的第一步,注冊(cè) Service Worker。

        // index.js 

        if ( 'serviceWorker' in navigator ) {
            navigator.serviceWorker.register('/sw.js')
            .then( registration => {
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            })
            .catch( err => console.log('ServiceWorker registration failed: ', err));
        }

        sw.js 中實(shí)現(xiàn)常規(guī)操作。

        // sw.js

        var cacheMaps = {
            cache_file'css.js',
            cache_image'images'
        }

        self.addEventListener('install', () => {
            // 一般注冊(cè)以后,激活需要等到再次刷新頁(yè)面后再激活
            // 可防止出現(xiàn)等待的情況,這意味著服務(wù)工作線程在安裝完后立即激活
            self.skipWaiting();
        })

        // 運(yùn)行觸發(fā)的事件
        self.addEventListener('activate', event => {
            event.waitUntil(
                // 若緩存數(shù)據(jù)更改,則在這里更新緩存
                caches.keys()
                .then( cacheNames => {
                    return cacheNames.filter( item => !Object.values(cacheMaps).includes(item))
                })
                .then( keys => {
                    return Promise.all( keys.map( key => {
                        return caches.delete(key);
                    }))
                })
                // 更新客戶端上的 Service Worker 腳本
                .then(() => self.clients.claim())
            )
        })

        實(shí)現(xiàn)網(wǎng)絡(luò)優(yōu)先的邏輯。

        function firstNet(cacheName, request{
            // 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)并緩存
            return fetch(request).then( response => {
                var responseCopy = response.clone();
                caches.open(cacheName).then( cache => {
                    cache.put(request, responseCopy);
                });
                return response;
            }).catch(() => {
                return caches.open(cacheName).then( cache => {
                    return cache.match(request);
                });
            });
        }

        實(shí)現(xiàn)緩存優(yōu)先的邏輯。

        function firstCache(cacheName, request{
            return caches.open(cacheName).then( cache => {
                return cache.match(request).then( response => {
                    var fetchServer = function({
                        return fetch(request).then( newResponse => {
                            cache.put(request, newResponse.clone());
                            return newResponse;
                        });
                    }
                    // 如果緩存中有數(shù)據(jù)則返回,否則請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
                    if (response) {
                        return response;
                    } else {
                        return fetchServer();
                    }
                });
            });
        }

        完成緩存策略中我們提到的第一點(diǎn),即對(duì) CSS 、JS 請(qǐng)求使用網(wǎng)絡(luò)優(yōu)先,圖片資源請(qǐng)求實(shí)現(xiàn)緩存優(yōu)先。

        // sw.js

        self.addEventListener('fetch', event => {
            var 
            request = event.request,
            url = request.url,
            cacheName;

            // 網(wǎng)絡(luò)優(yōu)先
            if ( /\.(js|css)$/.test(url) ) {
                (cacheName = cacheMaps.cache_file) && e.respondWith(firstNet(cacheName, request));
            }
            // 緩存優(yōu)先
            else if ( /\.(png|jpg|jpeg|gif|webp)$/.test(url) ) {
                (cacheName = cacheMaps.cache_image) && e.respondWith(firstCache(cacheName, request));
            }
        })

        接下來(lái)我們利用 Promise.race() 完成一個(gè)競(jìng)速模式,從而實(shí)現(xiàn)上文提到的第二點(diǎn)即根據(jù)網(wǎng)絡(luò)條件的好壞執(zhí)行相應(yīng)的操作。

        function networkCacheRace(cacheName, request{
            var timer, TIMEOUT = 500;
            /**
             * 網(wǎng)絡(luò)好的情況下給網(wǎng)絡(luò)請(qǐng)求500ms, 若超時(shí)則從緩存中取數(shù)據(jù)
             * 若網(wǎng)絡(luò)較差且沒(méi)有緩存, 由于第一個(gè) Promise 會(huì)一直處于 pending, 故此時(shí)等待網(wǎng)絡(luò)請(qǐng)求響應(yīng)
             */

            return Promise.race([new Promise((resolve, reject) => {
                timer = setTimeout(() => {
                    caches.open(cacheName).then( cache => {
                        cache.match(request).then( response => {
                            if (response) {
                                resolve(response);
                            }
                        });
                    });
                }, TIMEOUT);
            }), fetch(request).then( response => {
                clearTimeout(timer);
                var responseCopy = response.clone();
                caches.open(cacheName).then( cache => {
                    cache.put(request, responseCopy);
                });
                return response;
            }).catch(() => {
                clearTimeout(timer);
                return caches.open(cacheName).then( cache => {
                    return cache.match(request);
                });
            })]);
        }

        現(xiàn)在我們可以在 sw.js 中更改一下緩存策略,從而達(dá)到最理想的效果。

        // sw.js

        self.addEventListener('fetch', event => {
            // ...
            if ( /\.(js|css)$/.test(url) ) {
                (cacheName = cacheMaps.cache_file) 
                && e.respondWith(networkCacheRace(cacheName, request));
            }
            // ...
        })

        更好的方案 - Workbox

        什么是 Workbox ? 我們可以看看谷歌開(kāi)發(fā)者官網(wǎng)中給出的解釋。

        Workbox is a library that bakes in a set of best practices and removes the boilerplate every developer writes when working with service workers.

        其大概意思是它對(duì)常見(jiàn)的 Service Worker 操作進(jìn)行了一層封裝, 根據(jù)最佳實(shí)踐方便了開(kāi)發(fā)者的使用。因此在我們快速開(kāi)發(fā)自己的 PWA 應(yīng)用時(shí)使用 Workbox 是最合適不過(guò)的了。

        它主要有以下幾大功能 :

        • Precaching  ~  預(yù)緩存

        • Runtime caching  ~  運(yùn)行時(shí)緩存

        • Strategies  ~  緩存策略

        • Request routing  ~  請(qǐng)求路由控制

        • Background sync  ~  后臺(tái)同步

        • etc …

        基于本文的內(nèi)容, 在這里我們只談?wù)勅绾魏?jiǎn)單的使用 Workbox 以及它所提供的幾種緩存策略。

        注意在 index.js 里面的注冊(cè)操作不會(huì)改變, 變化的是 sw.js 中的代碼。

        // sw.js

        // 導(dǎo)入谷歌提供的 Workbox 庫(kù)
        importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js');

        if ( !workbox ) { 
            console.log(`Workbox didn't load.`);
            return;
        }

        // Workbox 注冊(cè)成功, 可以進(jìn)行下一步的操作

        // 立即激活, 跳過(guò)等待
        workbox.skipWaiting();
        workbox.clientsClaim();

        // workbox.routing.registerRoute()...

        下面用官網(wǎng)給出的幾張圖解釋一下 Workbox 所提供的幾種緩存策略,而它們正好能滿足上文我們自己用代碼所實(shí)現(xiàn)的效果。

        • Stale-While-Revalidate


        • Cache First


        • Network First


        • Cache Only


        • Network Only


        接下來(lái)讓我們使用 Workbox 去實(shí)現(xiàn)上文優(yōu)化前端性能的緩存策略。

        緩存優(yōu)先 :

        workbox.routing.registerRoute(
          /\.(png|jpg|jpeg|gif|webp)$/,
          // 對(duì)于圖片資源使用緩存優(yōu)先
          workbox.strategies.cacheFirst({
            cacheName'images',
            // 設(shè)置最大緩存數(shù)量以及過(guò)期時(shí)間
            plugins: [
              new workbox.expiration.Plugin({
                maxEntries60,
                maxAgeSeconds7 * 24 * 60 * 60,
              }),
            ],
          }),
        );

        網(wǎng)絡(luò)優(yōu)先 :

        workbox.routing.registerRoute(
          /\.(js|css)$/,
          workbox.strategies.staleWhileRevalidate({
            cacheName'css.js',
          }),
        );

        由上文圖中可看出 stale-while-revalidate 策略與我們實(shí)現(xiàn)的網(wǎng)絡(luò)優(yōu)先稍有不同,確切的來(lái)說(shuō)更加明智,因?yàn)槌说谝淮涡枰W(wǎng)絡(luò)請(qǐng)求,接下來(lái)的請(qǐng)求會(huì)直接從緩存中取數(shù)據(jù)但在頁(yè)面加載之后會(huì)立即更新緩存,這樣既保證了加載速度又能每次將數(shù)據(jù)準(zhǔn)確的更新到最新版本。

        競(jìng)速模式 :

        workbox.routing.registerRoute(
            /\.(js|css)$/,
            workbox.strategies.networkFirst({
                // 給網(wǎng)絡(luò)請(qǐng)求0.5秒,若仍未返回則從緩存中取數(shù)據(jù)
                networkTimetoutSeconds: 0.5,
                cacheName'css.js',
            }),
        );

        回頭看看我們手動(dòng)實(shí)現(xiàn)的緩存策略,顯然使用 Workbox 要簡(jiǎn)單的多。當(dāng)然 Workbox 中還有很多東西需要注意,但由于已經(jīng)超出了文章所講的主要內(nèi)容因此在這里無(wú)法具體闡述,建議讀者還是到官網(wǎng)去仔細(xì)看看文檔詳細(xì)了解一下,若因?yàn)閴Φ膯?wèn)題可以看看第二篇文章。

        Workbox ~ Google  ( 墻 )

        神奇的 Workbox 3.0


        后記

        如果你喜歡探討技術(shù),或者對(duì)本文有任何的意見(jiàn)或建議,非常歡迎加魚(yú)頭微信好友一起探討,當(dāng)然,魚(yú)頭也非常希望能跟你一起聊生活,聊愛(ài)好,談天說(shuō)地。魚(yú)頭的微信號(hào)是:krisChans95 也可以掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。公眾號(hào)窗口回復(fù)『 前端資料 』,即可獲取約 200M 前端面試資料,不要錯(cuò)過(guò)。


        瀏覽 95
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            99中文字幕在线观看 | 大胆少妇18P | 奇米91| 天干天干天夜夜爽 | 91精品国产91久久久久久软件优势 | 操妹妹网站 | 国产美女视频一区二区三区 | 亚洲伦理一区二区三区 | 婷婷色天使18禁久久yyy | 性处破╳╳╳高清欧美 |