`Service Worker` i JavaScript
Den här artikeln förklarar konceptet Service Worker
i JavaScript.
Vi förklarar steg för steg från grunderna i Service Worker
till praktisk cachehantering.
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>
style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}
javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>
Service Worker
i JavaScript
Service Worker
är en JavaScript-funktion som står mellan webbläsaren och nätverket, vilket möjliggör cachelagring av förfrågningar och offline-stöd. Det är en kärnteknik inom PWA (Progressive Web Apps) och ger webbapplikationer en upplevelse liknande den hos en inbyggd app.
Vad är en Service Worker
?
Service Worker
är en JavaScript-fil som körs i webbläsarens bakgrundstråd. Den körs på en separat tråd från sidan, kan inte komma åt användargränssnittet, men kan snappa upp nätverksförfrågningar, hantera cachelagring och hantera push-notiser.
De viktigaste funktionerna i en Service Worker
inkluderar följande:.
- Den fungerar endast via HTTPS, förutom på localhost.
- Den använder ett asynkront API baserat på Promise.
- Den är händelsestyrd och använder event som
install
,activate
,fetch
ochpush
.
Registrera en Service Worker
Låt oss först skriva koden för att registrera en Service Worker
i webbläsaren.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}
Förklaring
- Använd
navigator.serviceWorker.register()
för att registrera/sw.js
(Service Worker
-filen). - Du kan använda
then
för att hantera framgång ochcatch
för felhantering vid registrering. registration.scope
representerar det sökvägsområde (scope) som påverkas avService Worker
.- Som standard är scopet den katalog där den registrerade filen (i detta fall
/sw.js
) finns samt dess underkataloger.
Service Worker
-scope
Om du vill begränsa scopet kan du ange scope
med det andra argumentet i register
.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});
Förklaring
- Med denna inställning kommer endast sidor under
/app/
att kontrolleras avService Worker
.
Skapa Service Worker
-filen
Skapa sedan en fil som heter sw.js
och implementera de grundläggande händelserna.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];
Den här koden definierar en lista över resurser som ska cachas.
Roller och mekanismer för varje händelse
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});
self.addEventListener('install')
utlöses närService Worker
registreras för första gången. Vid detta steg för-cachas nödvändiga filer.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});
- Vid
activate
-händelsen tas gammal cache bort för att optimera lagring. Endast cache för den nya versionen sparas.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});
Alla HTTP-förfrågningar fångas upp—om en cachelagrad version finns returneras den, annars hämtas den från nätverket. Vid offline-läge returneras en alternativ sida (t.ex. offline.html
).
Bekräftar åtgärden
Låt oss faktiskt kontrollera hur Service Worker
fungerar.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});
- Här kontrollerar vi registreringen av
Service Worker
och beteendet vid hämtning av resurser genom att klicka på testknappen.
Exempel på cache-strategier
Följande är vanliga cache-strategier:.
Cache först
Här är ett exempel på implementering för 'Cache först'-strategin:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- Den här koden implementerar en cache-först-strategi, där den begärda resursen returneras från cachen om den finns tillgänglig; om inte, hämtas den från nätverket.
Nätverk först
Här är ett exempel på implementering för 'Nätverk först'-strategin:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});
- Den här koden implementerar en nätverk-först-strategi, där den begärda resursen först hämtas från nätverket och om det misslyckas, hämtas den från cachen.
Cacha endast stilmallar och JavaScript, hämta API:er i realtid
Här är ett exempel på implementering där stilmallar och JavaScript cachas medan API:er hämtas i realtid:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});
- Den här koden hanterar alltid API-förfrågningar i realtid och tillämpar en cache-först-strategi för statiska filer som stilmallar och JavaScript.
Uppdateringsflöde
Uppdateringsflödet för en Service Worker är följande:.
- En ny
sw.js
upptäcks. install
-händelsen startas.- Det väntar tills föregående
Service Worker
blir overksam. activate
-händelsen startas.- Den byter till den nya Service Worker.
- Händelsen
controllerchange
utlöses.
Uppdateringsdetektering
När en Service Worker
är installerad fortsätter den gamla användas tills nästa besök. För att tillämpa uppdateringar är det vanligt att använda kod som upptäcker uppdateringar och laddar om sidan.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
controllerchange
-händelsen utlöses när kontrollern för Service Workern, det vill säga Service Workern som styr den aktuella sidan, ändras.- Sidor som redan är öppna fortsätter att använda den nuvarande Service Workern, och den nyinstallerade Service Workern påverkar inte dessa sidor omedelbart. Därför används en teknik där
controllerchange
-händelsen används för att upptäcka att en ny controller har blivit aktiv och då laddas sidan om för att omedelbart tillämpa uppdateringen.
Varningar och bästa praxis
När du använder Service Worker
, tänk på följande punkter:.
-
HTTPS krävs På grund av säkerhetsbegränsningar fungerar det inte via
http://
förutom pålocalhost
. -
Hashade filnamn Cachens namn kan inkludera filnamn, URL och versionsinformation.
-
Kommunikation med klienter Använd
postMessage
för att kommunicera mellanService Worker
och sidans JavaScript.
Sammanfattning
Service Worker
är en viktig teknik för offline-stöd och prestandaförbättringar i webbappar. Genom att förstå det grundläggande flödet av installation, aktivering och hantering av hämtningar samt implementera lämpliga cache-strategier kan du bygga webbapplikationer av högre kvalitet.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.