aboutsummaryrefslogtreecommitdiff
path: root/files/ru/learn/javascript/client-side_web_apis/client-side_storage/index.html
blob: 4fe8ab08b7d8a511698cbcbe09fff7f2b80e32ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
---
title: Client-side storage
slug: Learn/JavaScript/Client-side_web_APIs/Client-side_storage
translation_of: Learn/JavaScript/Client-side_web_APIs/Client-side_storage
---
<p>{{LearnSidebar}}</p>

<div>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div>



<p class="summary">Современные веб-браузеры поддерживают несколько способов хранения данных из веб-сайтов на компьютере пользователя - с разрешения пользователя - чтобы потом получать их, когда это необходимо. Это позволяет долгосрочно хранить данные, сохранять сайты или документы для использования без подключения к сети, сохранять пользовательские настройки для вашего сайта и многое другое. В этой статье объясняются основы того, как это все работает.</p>

<table class="learn-box standard-table">
 <tbody>
  <tr>
   <th scope="row">Что нужно знать:</th>
   <td>JavaScript basics (see <a href="/en-US/docs/Learn/JavaScript/First_steps">first steps</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">building blocks</a>, <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a>), the <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">basics of Client-side APIs</a></td>
  </tr>
  <tr>
   <th scope="row">Цель статьи:</th>
   <td>To learn how to use client-side storage APIs to store application data.</td>
  </tr>
 </tbody>
</table>

<h2 id="Хранение_данных_на_стороне_клиента">Хранение данных на стороне клиента?</h2>

<p>Ранее, мы говорили о разнице между <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#Static_sites">статическими</a> и <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#Dynamic_sites">динамическими сайтами</a>. Большинство современных веб-сайтов являются динамическими - они хранят данные на сервере, используя какую-то базу данных (серверное хранилище), а затем запускают код <a href="/en-US/docs/Learn/Server-side">на стороне сервера</a> чтобы извлечь необходимые данные, вставить их в шаблоны статических страниц и передать полученный HTML-код клиенту для отображения в браузере пользователя.</p>

<p>Хранилище на стороне клиента работает по схожим принципам, но используется по-другому. Оно состоит из API-интерфейсов JavaScript, которые позволяют вам хранить данные на клиенте (то есть на компьютере пользователя), а затем извлекать их при необходимости. Это имеет много разных применений, таких как:</p>

<ul>
 <li>Персонализация настроек сайта (например, отображение выбранных пользователем виджетов, цветовой схемы или размера шрифта).</li>
 <li>Сохранение предыдущей активности на сайте (например, сохранение содержимого корзины покупок из предыдущего сеанса, запоминание, был ли пользователь ранее авторизован в системе).</li>
 <li>Сохранение данных и ресурсов локально, так что сайт будет быстрее (и, возможно, экономичнее) загружаться или использоваться без подключения к сети.</li>
 <li>Сохранение созданных веб-приложением документов локально для использования в автономном режиме.</li>
</ul>

<p>Часто, хранилища на сторонах клиента и сервера используются совместно. К примеру, вы должны загрузить из базы данных пакет музыкальных файлов для веб-игры, или музыкальный плеер хранит их в базе данных на стороне клиента, и воспроизводит по мере необходимости.</p>

<p>Пользователь должен будет загрузить музыкальные файлы только один раз - при последующих посещениях они будут извлечены из локальной базы данных.</p>

<div class="note">
<p><strong>Примечание.</strong> Существуют ограничения на объем данных, которые вы можете хранить с помощью API-интерфейсов на стороне клиента (возможно, как для отдельных API, так и в совокупности). Точный лимит варьируется в зависимости от браузера и, возможно, в зависимости от пользовательских настроек. Смотри <a href="/en-US/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria">Ограничения хранилища браузера и критерии переполнения</a> для большей информации.</p>
</div>

<h3 id="Старый_подход_куки">Старый подход: куки</h3>

<p>Концепция хранения на стороне клиента существует уже давно. С первых дней Интернета, использовали <a href="/en-US/docs/Web/HTTP/Cookies">cookies</a> для хранения информации, чтобы персонализировать пользовательский опыт на веб-сайтах. Это самая ранняя форма хранилища на стороне клиента, обычно используемая в Интернете.</p>

<p>Из-за этого возраста существует ряд проблем - как технических, так и с точки зрения пользовательского опыта - связанных с файлами cookie. Эти проблемы настолько значительны, что при первом посещении сайта людям, живущим в Европе, показываются сообщения, информирующие их о том, будут ли они использовать файлы cookie для хранения данных о них. Это связано с частью законодательства Европейского Союза, известного как  <a href="/en-US/docs/Web/HTTP/Cookies#EU_cookie_directive">EU Cookie directive</a>.</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/15734/cookies-notice.png" style="display: block; margin: 0 auto;"></p>

<p>По этим причинам мы не будем учить вас, как использовать куки в этой статье. Они устарели, у них множество <a href="/en-US/docs/Web/HTTP/Cookies#Security">проблем с безопасностью</a>, и неспособны хранить сложные данные. При этом существуют лучшие, более современные, способы хранения более широкого спектра данных на компьютере пользователя.</p>

<p>Единственным преимуществом файлов cookie является то, что они поддерживаются очень старыми браузерами, поэтому, если ваш проект требует, чтобы вы поддерживали устаревшие браузеры (например, Internet Explorer 8 или более ранние версии), файлы cookie могут по-прежнему быть полезными, но для большинства проектов вы не нужно больше прибегать к ним.</p>

<div class="note">
<p>Почему по-прежнему создаются новые сайты с использованием файлов cookie? Это происходит главным образом из-за привычек разработчиков, использования старых библиотек, которые всё ещё используют куки-файлы, и наличия множества веб-сайтов, предоставляющих устаревшие справочные и учебные материалы для обучения хранению данных.</p>
</div>

<h3 id="Новый_подход_Web_Storage_и_IndexedDB">Новый подход: Web Storage и IndexedDB</h3>

<p>Современные браузеры имеют гораздо более простые и эффективные API для хранения данных на стороне клиента, чем при использовании файлов cookie.</p>

<ul>
 <li>The <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> обеспечивает очень простой синтаксис для хранения и извлечения данных, состоящих из пар 'ключ' : 'значение'. Это полезно, когда вам просто нужно сохранить некоторые простые данные, такие как имя пользователя, вошли ли они в систему, какой цвет использовать для фона экрана и т. д.</li>
 <li>The <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a> обеспечивает браузер полной базой данных для хранения сложных данных. Это может быть использовано для хранения полных наборов записей клиентов и даже до сложных типов данных, таких как аудио или видео файлы.</li>
</ul>

<p>Вы узнаете больше об этих API ниже.</p>

<h3 id="Что_нас_ждёт_в_будущем_Cache_API">Что нас ждёт в будущем: Cache API</h3>

<p>Некоторые современные браузеры поддерживают новое {{domxref("Cache")}} API. Этот API предназначен для хранения HTTP-ответов на конкретные запросы и очень полезен для таких вещей, как хранение ресурсов сайта в автономном режиме, чтобы впоследствии сайт можно было использовать без сетевого подключения. Cache обычно используется в сочетании с <a href="/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a>, однако это не обязательно.</p>

<p>Использование Cache и Service Workers - сложная тема, и мы не будем подробно останавливаться на ней в этой статье, хотя приведём простой пример {{anch("Offline asset storage")}} в разделе ниже.</p>

<h2 id="Хранение_простых_данных_—_web_storage">Хранение простых данных — web storage</h2>

<p><a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> очень легко использовать — вы храните простые пары данных имя/значение (только строки, цифры и т.п.) и получаете их, когда необходимо.</p>

<h3 id="Базовый_синтаксис">Базовый синтаксис</h3>

<p>Посмотрите:</p>

<ol>
 <li>
  <p>Во-первых, посмотрите наши <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html">web storage шаблоны</a> на GitHub (откройте в новой вкладке).</p>
 </li>
 <li>
  <p>Откройте консоль инструментов JavaScript разработчика вашего браузера.</p>
 </li>
 <li>
  <p>Все данные вашего веб-хранилища содержатся в двух объектоподобных структурах внутри браузера: {{domxref("Window.sessionStorage", "sessionStorage")}} и {{domxref("Window.localStorage", "localStorage")}}. Первый сохраняет данные до тех пор, пока браузер открыт (данные теряются при закрытии браузера), а второй сохраняет данные даже после того, как браузер закрыт, а затем снова открыт. Мы будем использовать второй в этой статье, так как он, как правило, более полезен.</p>

  <p>{{domxref("Storage.setItem()")}} метод позволяет сохранить элемент данных в хранилище - он принимает два параметра: имя элемента и его значение. Попробуйте ввести это в свою консоль JavaScript (измените значение на своё собственное имя, если хотите!):</p>

  <pre class="brush: js">localStorage.setItem('name','Chris');</pre>
 </li>
 <li>
  <p>{{domxref("Storage.getItem()")}} метод принимает один параметр - имя элемента данных, который вы хотите получить - и возвращает значение элемента. Теперь введите эти строки в вашу консоль JavaScript:</p>

  <pre class="brush: js">var myName = localStorage.getItem('name');
myName</pre>

  <p>После ввода во второй строке вы должны увидеть, что переменная <code>myName</code> теперь содержит значение элемента данных <code>name</code>.</p>
 </li>
 <li>
  <p>{{domxref("Storage.removeItem()")}} метод принимает один параметр - имя элемента данных, который вы хотите удалить, - и удаляет этот элемент из веб-хранилища. Введите следующие строки в вашу консоль JavaScript:</p>

  <pre class="brush: js">localStorage.removeItem('name');
var myName = localStorage.getItem('name');
myName</pre>

  <p>Третья строка должна теперь возвращать ноль - элемент <code>name</code> больше не существует в веб-хранилище.</p>
 </li>
</ol>

<h3 id="Данные_сохраняются!">Данные сохраняются!</h3>

<p>Одной из ключевых особенностей веб-хранилища является то, что данные сохраняются между загрузками страниц (и даже в случае закрытия браузера, в случае <code>localStorage</code>). Давайте посмотрим на это в действии.</p>

<ol>
 <li>
  <p>Снова откройте пустой шаблон нашего веб-хранилища, но на этот раз в другом браузере, отличном от того, в котором вы открыли этот учебник! Так будет удобнее.</p>
 </li>
 <li>
  <p>Введите эти строки в консоль JavaScript браузера:</p>

  <pre class="brush: js">localStorage.setItem('name','Chris');
var myName = localStorage.getItem('name');
myName</pre>

  <p>Вы должны увидеть возвращённое имя элемента.</p>
 </li>
 <li>
  <p>Теперь закройте браузер и откройте его снова.</p>
 </li>
 <li>
  <p>Введите следующий код:</p>

  <pre class="brush: js">var myName = localStorage.getItem('name');
myName</pre>

  <p>Вы должны увидеть, что значение всё ещё доступно, даже после закрытия / открытия браузера.</p>
 </li>
</ol>

<h3 id="Для_каждого_домена_отдельное_хранилище">Для каждого домена отдельное хранилище</h3>

<p>Существуют отдельные хранилища данных для каждого домена (каждый отдельный веб-адрес загружается в браузер). Вы увидите, что если вы загрузите два веб-сайта (например, google.com и amazon.com) и попытаетесь сохранить элемент на одном веб-сайте, он не будет доступен для другого веб-сайта.</p>

<p>Это имеет смысл - вы можете представить себе проблемы безопасности, которые могут возникнуть, если веб-сайты смогут видеть данные друг друга!</p>

<h3 id="Более_развёрнутый_пример">Более развёрнутый пример</h3>

<p>Давайте применим эти новые знания, написав простой рабочий пример, чтобы дать вам представление о том, как можно использовать веб-хранилище. Наш пример позволит вам ввести имя, после чего страница обновится, чтобы дать вам персональное приветствие. Это состояние также будет сохраняться при перезагрузке страницы / браузера, поскольку имя хранится в веб-хранилище.</p>

<p>Вы можете найти пример HTML на <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> — он содержит простой веб-сайт с заголовком, контентом и нижним колонтитулом, а также форму для ввода вашего имени.</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/15735/web-storage-demo.png" style="display: block; margin: 0 auto;"></p>

<p>Давайте создадим пример, чтобы вы могли понять, как он работает.</p>

<ol>
 <li>
  <p>Во-первых, сделайте локальную копию нашего  <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> файла в новом каталоге на вашем компьютере.</p>
 </li>
 <li>
  <p>Далее обратите внимание, как наш HTML ссылается на файл JavaScript с именем <code>index.js</code> (см. строку 40). Нам нужно создать его, и записать в него наш код JavaScript. Создайте файл <code>index.js</code> в том же каталоге, что и ваш HTML-файл.</p>
 </li>
 <li>
  <p>Мы начнём с создания ссылок на все функции HTML, которыми мы должны манипулировать в этом примере - мы создадим их все как константы, поскольку эти ссылки не нужно изменять в жизненном цикле приложения. Добавьте следующие строки в ваш файл JavaScript:</p>

  <pre class="brush: js">// create needed constants
const rememberDiv = document.querySelector('.remember');
const forgetDiv = document.querySelector('.forget');
const form = document.querySelector('form');
const nameInput = document.querySelector('#entername');
const submitBtn = document.querySelector('#submitname');
const forgetBtn = document.querySelector('#forgetname');

const h1 = document.querySelector('h1');
const personalGreeting = document.querySelector('.personal-greeting');</pre>
 </li>
 <li>
  <p>Далее нам нужно включить небольшой обработчик событий, чтобы форма фактически не отправляла себя при нажатии кнопки отправки, так как это не то поведение, которое нам нужно. Добавьте этот фрагмент ниже вашего предыдущего кода:</p>

  <pre class="brush: js">// Stop the form from submitting when a button is pressed
form.addEventListener('submit', function(e) {
  e.preventDefault();
});</pre>
 </li>
 <li>
  <p>Теперь нам нужно добавить обработчик событий, функция-обработчик которого будет запускаться при нажатии кнопки «Say hello». В комментариях подробно объясняется, что делает каждый бит, но в сущности здесь мы берём имя, которое пользователь ввёл в поле ввода текста, и сохраняем его в веб-хранилище с помощью <code>setItem()</code>, затем запускаем функцию <code>nameDisplayCheck()</code>, которая будет обрабатывать обновление фактического текста сайта. Добавьте это в конец: </p>

  <pre class="brush: js">// run function when the 'Say hello' button is clicked
submitBtn.addEventListener('click', function() {
  // store the entered name in web storage
  localStorage.setItem('name', nameInput.value);
  // run nameDisplayCheck() to sort out displaying the
  // personalized greetings and updating the form display
  nameDisplayCheck();
});</pre>
 </li>
 <li>
  <p>На этом этапе нам также необходим обработчик событий для запуска функции при нажатии кнопки «Forget» — она будет отображена только после того как кнопка «Say hello» будет нажата (две формы состояния для переключения между ними). В этой функции мы удаляем переменную <code>name</code> из веб-хранилища используя <code>removeItem()</code>, затем снова запускаем <code>nameDisplayCheck()</code> для обновления. Добавьте этот код в конец:</p>

  <pre class="brush: js">// run function when the 'Forget' button is clicked
forgetBtn.addEventListener('click', function() {
  // Remove the stored name from web storage
  localStorage.removeItem('name');
  // run nameDisplayCheck() to sort out displaying the
  // generic greeting again and updating the form display
  nameDisplayCheck();
});</pre>
 </li>
 <li>
  <p>Самое время для определения самой функции <code>nameDisplayCheck()</code>. Здесь мы проверяем была ли переменная <code>name</code> сохранена в веб-хранилище с помощью <code>localStorage.getItem('name')</code> в качестве условия. Если переменная <code>name</code> была сохранена, то вызов вернёт - <code>true</code>; если же нет, то - <code>false</code>. Если <code>true</code>, мы показываем персональное приветствие, отображаем кнопку «Forget», и скрываем кнопку «Say hello». Если же <code>false</code>, мы отображаем общее приветствие и делаем обратное. Опять же, добавьте следующий код в конец:</p>

  <pre class="brush: js">// define the nameDisplayCheck() function
function nameDisplayCheck() {
  // check whether the 'name' data item is stored in web Storage
  if(localStorage.getItem('name')) {
    // If it is, display personalized greeting
    let name = localStorage.getItem('name');
    h1.textContent = 'Welcome, ' + name;
    personalGreeting.textContent = 'Welcome to our website, ' + name + '! We hope you have fun while you are here.';
    // hide the 'remember' part of the form and show the 'forget' part
    forgetDiv.style.display = 'block';
    rememberDiv.style.display = 'none';
  } else {
    // if not, display generic greeting
    h1.textContent = 'Welcome to our website ';
    personalGreeting.textContent = 'Welcome to our website. We hope you have fun while you are here.';
    // hide the 'forget' part of the form and show the 'remember' part
    forgetDiv.style.display = 'none';
    rememberDiv.style.display = 'block';
  }
}</pre>
 </li>
 <li>
  <p>Последнее но не менее важное, нам необходимо запускать функцию <code>nameDisplayCheck()</code> при каждой загрузке страницы. Если мы не сделаем этого, персональное приветствие не будет сохранятся после перезагрузки страницы. Добавьте следующий фрагмент в конец вашего кода:</p>

  <pre class="brush: js">document.body.onload = nameDisplayCheck;</pre>
 </li>
</ol>

<p>Ваш пример закончен — отличная работа! Всё что теперь осталось это сохранить ваш код и протестировать вашу HTML страницу в браузере. Вы можете посмотреть нашу <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html">завершённую версию работающую здесь</a>.</p>

<div class="note">
<p>Есть и другой, немного более комплексный пример описываемый в <a href="/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">Using the Web Storage API</a>.</p>
</div>

<h2 id="Храним_более_сложную_информацию_в_IndexedDB">Храним более сложную информацию в IndexedDB</h2>

<p><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a> (иногда сокращают до IDB) это полная база данных, доступная в браузере, в которой вы можете хранить сложные связанные данные, типы которых не ограничиваются простыми значениями, такими как строки или числа.</p>

<p>Вы можете сохранить видео, фото, и почти все остальные файлы с IndexedDB. </p>

<p dir="ltr" id="tw-target-text">Однако это обходится дорого: IndexedDB гораздо сложнее в использовании, чем Web Storage API.</p>

<p dir="ltr" id="tw-target-text">В этом разделе мы действительно только коснёмся того, на что он способен, но мы дадим вам достаточно, чтобы начать.</p>

<h3 dir="ltr" id="Работа_с_примером_хранения_заметок">Работа с примером хранения заметок</h3>

<p>Here we'll run you through an example that allows you to store notes in your browser and view and delete them whenever you like, getting you to build it up for yourself and explaining the most fundamental parts of IDB as we go along.</p>

<p>The app looks something like this:</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/15744/idb-demo.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p>

<p>Each note has a title and some body text, each individually editable. The JavaScript code we'll go through below has detailed comments to help you understand what's going on.</p>

<h3 id="Предустановка">Предустановка</h3>

<ol>
 <li>First of all, make local copies of our <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.html">index.html</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/style.css">style.css</a></code>, and <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index-start.js">index-start.js</a></code> files into a new directory on your local machine.</li>
 <li>Have a look at the files. You'll see that the HTML is pretty simple: a web site with a header and footer, as well as a main content area that contains a place to display notes, and a form for entering new notes into the database. The CSS provides some simple styling to make it clearer what is going on. The JavaScript file contains five declared constants containing references to the {{htmlelement("ul")}} element the notes will be displayed in, the title and body {{htmlelement("input")}} elements, the {{htmlelement("form")}} itself, and the {{htmlelement("button")}}.</li>
 <li>Rename your JavaScript file to <code>index.js</code>. You are now ready to start adding code to it.</li>
</ol>

<h3 id="Настраиваем_базу_данных">Настраиваем базу данных</h3>

<p>Now let's look at what we have to do in the first place, to actually set up a database.</p>

<ol>
 <li>
  <p>Below the constant declarations, add the following lines:</p>

  <pre class="brush: js">// Create an instance of a db object for us to store the open database in
let db;</pre>

  <p>Here we are declaring a variable called <code>db</code> — this will later be used to store an object representing our database. We will use this in a few places, so we've declared it globally here to make things easier.</p>
 </li>
 <li>
  <p>Next, add the following to the bottom of your code:</p>

  <pre class="brush: js">window.onload = function() {

};</pre>

  <p>We will write all of our subsequent code inside this <code>window.onload</code> event handler function, called when the window's {{event("load")}} event fires, to make sure we don't try to use IndexedDB functionality before the app has completely finished loading (it could fail if we don't).</p>
 </li>
 <li>
  <p>Inside the <code>window.onload</code> handler, add the following:</p>

  <pre class="brush: js">// Open our database; it is created if it doesn't already exist
// (see onupgradeneeded below)
let request = window.indexedDB.open('notes', 1);</pre>

  <p>This line creates a <code>request</code> to open version <code>1</code> of a database called <code>notes</code>. If this doesn't already exist, it will be created for you by subsequent code. You will see this request pattern used very often throughout IndexedDB. Database operations take time. You don't want to hang the browser while you wait for the results, so database operations are {{Glossary("asynchronous")}}, meaning that instead of happening immediately, they will happen at some point in the future, and you get notified when they're done.</p>

  <p>To handle this in IndexedDB, you create a request object (which can be called anything you like — we called it <code>request</code> so it is obvious what it is for). You then use event handlers to run code when the request completes, fails, etc., which you'll see in use below.</p>

  <div class="note">
  <p><strong>Note</strong>: The version number is important. If you want to upgrade your database (for example, by changing the table structure), you have to run your code again with an increased version number, different schema specified inside the <code>onupgradeneeded</code> handler (see below), etc. We won't cover upgrading databases in this simple tutorial.</p>
  </div>
 </li>
 <li>
  <p>Now add the following event handlers just below your previous addition — again inside the <code>window.onload</code> handler:</p>

  <pre class="brush: js">// onerror handler signifies that the database didn't open successfully
request.onerror = function() {
  console.log('Database failed to open');
};

// onsuccess handler signifies that the database opened successfully
request.onsuccess = function() {
  console.log('Database opened successfully');

  // Store the opened database object in the db variable. This is used a lot below
  db = request.result;

  // Run the displayData() function to display the notes already in the IDB
  displayData();
};</pre>

  <p>The {{domxref("IDBRequest.onerror", "request.onerror")}} handler will run if the system comes back saying that the request failed. This allows you to respond to this problem. In our simple example, we just print a message to the JavaScript console.</p>

  <p>The {{domxref("IDBRequest.onsuccess", "request.onsuccess")}} handler on the other hand will run if the request returns successfully, meaning the database was successfully opened. If this is the case, an object representing the opened database becomes available in the {{domxref("IDBRequest.result", "request.result")}} property, allowing us to manipulate the database. We store this in the <code>db</code> variable we created earlier for later use. We also run a custom function called <code>displayData()</code>, which displays the data in the database inside the {{HTMLElement("ul")}}. We run it now so that the notes already in the database are displayed as soon as the page loads. You'll see this defined later on.</p>
 </li>
 <li>
  <p>Finally for this section, we'll add probably the most important event handler for setting up the database: {{domxref("IDBOpenDBRequest.onupgradeneeded", "request.onupdateneeded")}}. This handler runs if the database has not already been set up, or if the database is opened with a bigger version number than the existing stored database (when performing an upgrade). Add the following code, below your previous handler:</p>

  <pre class="brush: js">// Setup the database tables if this has not already been done
request.onupgradeneeded = function(e) {
  // Grab a reference to the opened database
  let db = e.target.result;

  // Create an objectStore to store our notes in (basically like a single table)
  // including a auto-incrementing key
  let objectStore = db.createObjectStore('notes', { keyPath: 'id', autoIncrement:true });

  // Define what data items the objectStore will contain
  objectStore.createIndex('title', 'title', { unique: false });
  objectStore.createIndex('body', 'body', { unique: false });

  console.log('Database setup complete');
};</pre>

  <p>This is where we define the schema (structure) of our database; that is, the set of columns (or fields) it contains. Here we first grab a reference to the existing database from <code>e.target.result</code> (the event target's <code>result</code> property), which is the <code>request</code> object. This is equivalent to the line <code>db = request.result;</code> inside the <code>onsuccess</code> handler, but we need to do this separately here because the <code>onupgradeneeded</code> handler (if needed) will run before the <code>onsuccess</code> handler, meaning that the <code>db</code> value wouldn't be available if we didn't do this.</p>

  <p>We then use {{domxref("IDBDatabase.createObjectStore()")}} to create a new object store inside our opened database. This is equivalent to a single table in a conventional database system. We've given it the name notes, and also specified an <code>autoIncrement</code> key field called <code>id</code> — in each new record this will automatically be given an incremented value — the developer doesn't need to set this explicitly. Being the key, the <code>id</code> field will be used to uniquely identify records, such as when deleting or displaying a record.</p>

  <p>We also create two other indexes (fields) using the {{domxref("IDBObjectStore.createIndex()")}} method: <code>title</code> (which will contain a title for each note), and <code>body</code> (which will contain the body text of the note).</p>
 </li>
</ol>

<p>So with this simple database schema set up, when we start adding records to the database each one will be represented as an object along these lines:</p>

<pre class="brush: js"><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body"><span class="objectBox objectBox-object"><span class="objectLeftBrace">{
  </span><span class="nodeName">title</span><span class="objectEqual">: </span><span class="objectBox objectBox-string">"Buy milk"</span>,
  <span class="nodeName">body</span><span class="objectEqual">: </span><span class="objectBox objectBox-string">"Need both cows milk and soya."</span>,
  <span class="nodeName">id</span><span class="objectEqual">: </span><span class="objectBox objectBox-number">8</span></span></span></span></span><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body"><span class="objectBox objectBox-object"><span class="objectRightBrace">
}</span></span></span></span></span></pre>

<h3 id="Добавляем_данные_в_базу_данных">Добавляем данные в базу данных</h3>

<p>Now let's look at how we can add records to the database. This will be done using the form on our page.</p>

<p>Below your previous event handler (but still inside the <code>window.onload</code> handler), add the following line, which sets up an <code>onsubmit</code> handler that runs a function called <code>addData()</code> when the form is submitted (when the submit {{htmlelement("button")}} is pressed leading to a successful form submission):</p>

<pre class="brush: js">// Create an onsubmit handler so that when the form is submitted the addData() function is run
form.onsubmit = addData;</pre>

<p>Now let's define the <code>addData()</code> function. Add this below your previous line:</p>

<pre class="brush: js">// Define the addData() function
function addData(e) {
  // prevent default - we don't want the form to submit in the conventional way
  e.preventDefault();

  // grab the values entered into the form fields and store them in an object ready for being inserted into the DB
  let newItem = { title: titleInput.value, body: bodyInput.value };

  // open a read/write db transaction, ready for adding the data
  let transaction = db.transaction(['notes'], 'readwrite');

  // call an object store that's already been added to the database
  let objectStore = transaction.objectStore('notes');

  // Make a request to add our newItem object to the object store
  var request = objectStore.add(newItem);
  request.onsuccess = function() {
    // Clear the form, ready for adding the next entry
    titleInput.value = '';
    bodyInput.value = '';
  };

  // Report on the success of the transaction completing, when everything is done
  transaction.oncomplete = function() {
    console.log('Transaction completed: database modification finished.');

    // update the display of data to show the newly added item, by running displayData() again.
    displayData();
  };

  transaction.onerror = function() {
    console.log('Transaction not opened due to error');
  };
}</pre>

<p>This is quite complex; breaking it down, we:</p>

<ul>
 <li>Run {{domxref("Event.preventDefault()")}} on the event object to stop the form actually submitting in the conventional manner (this would cause a page refresh and spoil the experience).</li>
 <li>Create an object representing a record to enter into the database, populating it with values from the form inputs. note that we don't have to explicitly include an <code>id</code> value — as we expained early, this is auto-populated.</li>
 <li>Open a <code>readwrite</code> transaction against the <code>notes</code> object store using the {{domxref("IDBDatabase.transaction()")}} method. This transaction object allows us to access the object store so we can do something to it, e.g. add a new record.</li>
 <li>Access the object store using the {{domxref("IDBTransaction.objectStore")}} property, saving it in the <code>objectStore</code> variable.</li>
 <li>Add the new record to the database using {{domxref("IDBObjectStore.add()")}}. This creates a request object, in the same fashion as we've seen before.</li>
 <li>Add a bunch of event handlers to the <code>request</code> and the <code>transaction</code> to run code at critical points in the lifecycle. Once the request has succeeded, we clear the form inputs ready for entering the next note. Once the transaction has completed, we run the <code>displayData()</code> function again to update the display of notes on the page.</li>
</ul>

<h3 id="Отображаем_данные">Отображаем данные</h3>

<p>We've referenced <code>displayData()</code> twice in our code already, so we'd probably better define it. Add this to your code, below the previous function definition:</p>

<pre class="brush: js">// Define the displayData() function
function displayData() {
  // Here we empty the contents of the list element each time the display is updated
  // If you ddn't do this, you'd get duplicates listed each time a new note is added
  while (list.firstChild) {
    list.removeChild(list.firstChild);
  }

  // Open our object store and then get a cursor - which iterates through all the
  // different data items in the store
  let objectStore = db.transaction('notes').objectStore('notes');
  objectStore.openCursor().onsuccess = function(e) {
    // Get a reference to the cursor
    let cursor = e.target.result;

    // If there is still another data item to iterate through, keep running this code
    if(cursor) {
      // Create a list item, h3, and p to put each data item inside when displaying it
      // structure the HTML fragment, and append it inside the list
      let listItem = document.createElement('li');
      let h3 = document.createElement('h3');
      let para = document.createElement('p');

      listItem.appendChild(h3);
      listItem.appendChild(para);
      list.appendChild(listItem);

      // Put the data from the cursor inside the h3 and para
      h3.textContent = cursor.value.title;
      para.textContent = cursor.value.body;

      // Store the ID of the data item inside an attribute on the listItem, so we know
      // which item it corresponds to. This will be useful later when we want to delete items
      listItem.setAttribute('data-note-id', cursor.value.id);

      // Create a button and place it inside each listItem
      let deleteBtn = document.createElement('button');
      listItem.appendChild(deleteBtn);
      deleteBtn.textContent = 'Delete';

      // Set an event handler so that when the button is clicked, the deleteItem()
      // function is run
      deleteBtn.onclick = function(e) {
        deleteItem(e);
      };

      // Iterate to the next item in the cursor
      cursor.continue();
    } else {
      // Again, if list item is empty, display a 'No notes stored' message
      if(!list.firstChild) {
        let listItem = document.createElement('li');
        listItem.textContent = 'No notes stored.'
        list.appendChild(listItem);
      }
      // if there are no more cursor items to iterate through, say so
      console.log('Notes all displayed');
    }
  };
}</pre>

<p>Again, let's break this down:</p>

<ul>
 <li>First we empty out the {{htmlelement("ul")}} element's content, before then filling it with the updated content. If you didn't do this, you'd end up with a huge list of duplicated content being added to with each update.</li>
 <li>Next, we get a reference to the <code>notes</code> object store using {{domxref("IDBDatabase.transaction()")}} and {{domxref("IDBTransaction.objectStore")}} like we did in <code>addData()</code>, except here we are chaining them together in one line.</li>
 <li>The next step is to use {{domxref("IDBObjectStore.openCursor()")}} method to open a request for a cursor — this is a construct that can be used to iterate over the records in an object store. We chain an <code>onsuccess</code> handler on to the end of this line to make the code more concise — when the cursor is successfully returned, the handler is run.</li>
 <li>We get a reference to the cursor itself (an {{domxref("IDBCursor")}} object) using let <code>cursor = e.target.result</code>.</li>
 <li>Next, we check to see if the cursor contains a record from the datastore (<code>if(cursor){ ... }</code>) — if so, we create a DOM fragment, populate it with the data from the record, and insert it into the page (inside the <code>&lt;ul&gt;</code> element). We also include a delete button that, when clicked, will delete that note by running the <code>deleteItem()</code> function, which we will look at in the next section.</li>
 <li>At the end of the <code>if</code> block, we use the {{domxref("IDBCursor.continue()")}} method to advance the cursor to the next record in the datastore, and run the content of the <code>if</code> block again. If there is another record to iterate to, this causes it to be inserted into the page, and then <code>continue()</code> is run again, and so on.</li>
 <li>When there are no more records to iterate over, <code>cursor</code> will return <code>undefined</code>, and therefore the <code>else</code> block will run instead of the <code>if</code> block. This block checks whether any notes were inserted into the <code>&lt;ul&gt;</code> — if not, it inserts a message to say no note was stored.</li>
</ul>

<h3 id="Удаляем_данные">Удаляем данные</h3>

<p>As stated above, when a note's delete button is pressed, the note is deleted. This is achieved by the <code>deleteItem()</code> function, which looks like so:</p>

<pre class="brush: js">// Define the deleteItem() function
function deleteItem(e) {
  // retrieve the name of the task we want to delete. We need
  // to convert it to a number before trying it use it with IDB; IDB key
  // values are type-sensitive.
  let noteId = Number(e.target.parentNode.getAttribute('data-note-id'));

  // open a database transaction and delete the task, finding it using the id we retrieved above
  let transaction = db.transaction(['notes'], 'readwrite');
  let objectStore = transaction.objectStore('notes');
  let request = objectStore.delete(noteId);

  // report that the data item has been deleted
  transaction.oncomplete = function() {
    // delete the parent of the button
    // which is the list item, so it is no longer displayed
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
    console.log('Note ' + noteId + ' deleted.');

    // Again, if list item is empty, display a 'No notes stored' message
    if(!list.firstChild) {
      let listItem = document.createElement('li');
      listItem.textContent = 'No notes stored.';
      list.appendChild(listItem);
    }
  };
}</pre>

<ul>
 <li>The first part of this could use some explaining — we retrieve the ID of the record to be deleted using <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> — recall that the ID of the record was saved in a <code>data-note-id</code> attribute on the <code>&lt;li&gt;</code> when it was first displayed. We do however need to pass the attribute through the global built-in <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number">Number()</a> object, as it is currently a string, and otherwise won't be recognized by the database.</li>
 <li>We then get a reference to the object store using the same pattern we've seen previously, and use the {{domxref("IDBObjectStore.delete()")}} method to delete the record from the database, passing it the ID.</li>
 <li>When the database transaction is complete, we delete the note's <code>&lt;li&gt;</code> from the DOM, and again do the check to see if the <code>&lt;ul&gt;</code> is now empty, inserting a note as appropriate.</li>
</ul>

<p>So that's it! Your example should now work.</p>

<p>If you are having trouble with it, feel free to <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/">check it against our live example</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.js">source code</a> also).</p>

<h3 id="Храним_сложные_данные_через_IndexedDB">Храним сложные данные через IndexedDB</h3>

<p>As we mentioned above, IndexedDB can be used to store more than just simple text strings. You can store just about anything you want, including complex objects such as video or image blobs. And it isn't much more difficult to achieve than any other type of data.</p>

<p>To demonstrate how to do it, we've written another example called <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/indexeddb/video-store">IndexedDB video store</a> (see it <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/">running live here also</a>). When you first run the example, it downloads all the videos from the network, stores them in an IndexedDB database, and then displays the videos in the UI inside {{htmlelement("video")}} elements. The second time you run it, it finds the videos in the database and gets them from there instead befoire displaying them — this makes subsequent loads much quicker and less bandwidth-hungry.</p>

<p>Let's walk through the most interesting parts of the example. We won't look at it all — a lot of it is similar to the previous example, and the code is well-commented.</p>

<ol>
 <li>
  <p>For this simple example, we've stored the names of the videos to fetch in an array of objects:</p>

  <pre class="brush: js">const videos = [
  { 'name' : 'crystal' },
  { 'name' : 'elf' },
  { 'name' : 'frog' },
  { 'name' : 'monster' },
  { 'name' : 'pig' },
  { 'name' : 'rabbit' }
];</pre>
 </li>
 <li>
  <p>To start with, once the database is successfully opened we run an <code>init()</code> function. This loops through the different video names, trying to load a record identified by each name from the <code>videos</code> database.</p>

  <p>If each video is found in the database (easily checked by seeing whether <code>request.result</code> evaluates to <code>true</code> — if the record is not present, it will be <code>undefined</code>), its video files (stored as blobs) and the video name are passed straight to the <code>displayVideo()</code> function to place them in the UI. If not, the video name is passed to the <code>fetchVideoFromNetwork()</code> function to ... you guessed it — fetch the video from the network.</p>

  <pre class="brush: js">function init() {
  // Loop through the video names one by one
  for(let i = 0; i &lt; videos.length; i++) {
    // Open transaction, get object store, and get() each video by name
    let objectStore = db.transaction('videos').objectStore('videos');
    let request = objectStore.get(videos[i].name);
    request.onsuccess = function() {
      // If the result exists in the database (is not undefined)
      if(request.result) {
        // Grab the videos from IDB and display them using displayVideo()
        console.log('taking videos from IDB');
        displayVideo(request.result.mp4, request.result.webm, request.result.name);
      } else {
        // Fetch the videos from the network
        fetchVideoFromNetwork(videos[i]);
      }
    };
  }
}</pre>
 </li>
 <li>
  <p>The following snippet is taken from inside <code>fetchVideoFromNetwork()</code> — here we fetch MP4 and WebM versions of the video using two separate {{domxref("fetch()", "WindowOrWorkerGlobalScope.fetch()")}} requests. We then use the {{domxref("blob()", "Body.blob()")}} method to extract each response's body as a blob, giving us an object representation of the videos that can be stored and displayed later on.</p>

  <p>We have a problem here though — these two requests are both asynchronous, but we only want to try to display or store the video when both promises have fulfilled. Fortunately there is a built-in method that handles such a problem — {{jsxref("Promise.all()")}}. This takes one argument — references to all the individual promises you want to check for fulfillment placed in an array — and is itself promise-based.</p>

  <p>When all those promises have fulfilled, the <code>all()</code> promise fulfills with an array containing all the individual fulfillment values. Inside the <code>all()</code> block, you can see that we then call the <code>displayVideo()</code> function like we did before to display the videos in the UI, then we also call the <code>storeVideo()</code> function to store those videos inside the database.</p>

  <pre class="brush: js">let mp4Blob = fetch('videos/' + video.name + '.mp4').then(response =&gt;
  response.blob()
);
let webmBlob = fetch('videos/' + video.name + '.webm').then(response =&gt;
  response.blob()
);;

// Only run the next code when both promises have fulfilled
Promise.all([mp4Blob, webmBlob]).then(function(values) {
  // display the video fetched from the network with displayVideo()
  displayVideo(values[0], values[1], video.name);
  // store it in the IDB using storeVideo()
  storeVideo(values[0], values[1], video.name);
});</pre>
 </li>
 <li>
  <p>Let's look at <code>storeVideo()</code> first. This is very similar to the pattern you saw in the previous example for adding data to the database — we open a <code>readwrite</code> transaction and get an object store reference our <code>videos</code>, create an object representing the record to add to the database, then simply add it using {{domxref("IDBObjectStore.add()")}}.</p>

  <pre class="brush: js">function storeVideo(mp4Blob, webmBlob, name) {
  // Open transaction, get object store; make it a readwrite so we can write to the IDB
  let objectStore = db.transaction(['videos'], 'readwrite').objectStore('videos');
  // Create a record to add to the IDB
  let record = {
    mp4 : mp4Blob,
    webm : webmBlob,
    name : name
  }

  // Add the record to the IDB using add()
  let request = objectStore.add(record);

  ...

};</pre>
 </li>
 <li>
  <p>Last but not least, we have <code>displayVideo()</code>, which creates the DOM elements needed to insert the video in the UI and then appends them to the page. The most interesting parts of this are those shown below — to actually display our video blobs in a <code>&lt;video&gt;</code> element, we need to create object URLs (internal URLs that point to the video blobs stored in memory) using the {{domxref("URL.createObjectURL()")}} method. Once that is done, we can set the object URLs to be the vaues of our {{htmlelement("source")}} element's <code>src</code> attributes, and it works fine.</p>

  <pre class="brush: js">function displayVideo(mp4Blob, webmBlob, title) {
  // Create object URLs out of the blobs
  let mp4URL = URL.createObjectURL(mp4Blob);
  let webmURL = URL.createObjectURL(webmBlob);

  ...

  let video = document.createElement('video');
  video.controls = true;
  let source1 = document.createElement('source');
  source1.src = mp4URL;
  source1.type = 'video/mp4';
  let source2 = document.createElement('source');
  source2.src = webmURL;
  source2.type = 'video/webm';

  ...
}</pre>
 </li>
</ol>

<h2 id="Офлайн_хранение_данных">Офлайн-хранение данных</h2>

<p>Пример ниже показывает, как создать приложение, которое будет хранить данные большого объёма в хранилище IndexedDB, избегая необходимости скачивать их повторно. Это важное улучшение пользовательского опыта, но есть одно замечание — основной HTML, CSS, и файлы JavaScript все ещё нужно загружать каждый раз при запросе сайта, это значит, что данный пример не будет работать при отсутствии сетевого соединения.</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/15759/ff-offline.png" style="border-style: solid; border-width: 1px; display: block; height: 307px; margin: 0px auto; width: 765px;"></p>

<p>Это тот случай, когда <a href="/en-US/docs/Web/API/Service_Worker_API">Service workers</a> и <a href="/en-US/docs/Web/API/Cache">Cache API</a> приходят на помощь.</p>

<p>Сервис-воркер это файл JavaScript, который регистрируется на конкретном источнике (веб-сайте или части сайта на конкретном домене) при обращении браузером. После регистрации, он может управлять страницами на этом источнике. Воркер находится между загруженной страницей и сетевым соединением, перехватывая сетевые запросы источника.</p>

<p>Когда worker перехватывает запрос, он может делать многие вещи (смотри <a href="/en-US/docs/Web/API/Service_Worker_API#Other_use_case_ideas">идеи для использования сервис-воркеров</a>), но классический пример это сохранение сетевых ответов и затем доступ к ним при запросе, вместо запросов по сети. В результате, это позволяет сделать веб-сайт полностью работающим в офлайне.</p>

<p><a href="/en-US/docs/Web/API/Cache">Cache API</a> это ещё один механизм хранения данных на клиенте с небольшим отличием — он разработан для хранения HTTP ответов, и прекрасно работает с сервис-воркерами.</p>

<div class="note">
<p><strong>Note</strong>: Service workers и Cache доступны в большинстве современных браузеров. В момент написания статьи, Safari ещё не имел реализации, но скоро должна быть.</p>
</div>

<h3 id="Пример_сервис_воркера">Пример сервис воркера</h3>

<p>Давайте взглянем на пример, чтобы дать вам немного мыслей о том, что из этого может выйти. Мы создали другую версию примера хранения видео, который использовался в предыдущей секции — эта функциональность идентична, за исключением того, что этот пример также сохраняет HTML, CSS, и JavaScript в Cache API посредством сервис-воркеров, что позволяет приложению работать полностью в офлайне!</p>

<p>Смотри пример <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/">хранилище видео с IndexedDB и сервис-воркером</a>, и его <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/cache-sw/video-store-offline">исходный код</a>.</p>

<h4 id="Регистрируем_сервис_воркер">Регистрируем сервис воркер</h4>

<p>Первое, что нужно заметить, это дополнительный кусок кода, расположенный в основном JavaScript файле (см. <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js">index.js</a>). Первое,что мы делаем, это проверка на то, что <code>serviceWorker</code> доступен в объекте {{domxref("Navigator")}}. Если этот так, тогда мы знаем, что как минимум, базовые функции сервис-воркера доступны. Внутри проверки мы используем метод {{domxref("ServiceWorkerContainer.register()")}} для регистрации сервис-воркера, находящегося в файле <code>sw.js</code> на текущем источнике, таким образом, он может управлять страницами в текущей или внутренних директориях. Когда промис выполнится, сервис-воркер считается зарегистрированным.</p>

<pre class="brush: js">  // Регистрация сервис-воркера для обеспечения доступности сайта в офлайне

  if('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js')
             .then(function() { console.log('Service Worker зарегистрирован'); });
  }</pre>

<div class="note">
<p><strong>Примечание</strong>: Путь к файлу <code>sw.js</code> указан относительно корня сайта, а не JavaScript файла, содержащего основной код. Полный путь - <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. Корень -  <code>https://mdn.github.io</code>, и следовательно указываемый путь должен быть <code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>. Если вы хотите использовать данный пример на своём сервере, вы также должны изменить путь к скрипту. Это довольно запутанно, но обязано так работать по причинам безопасности.</p>
</div>

<h4 id="Устанавливаем_сервис_воркер">Устанавливаем сервис воркер</h4>

<p>В следующий раз, когда страница с сервис-воркером будет запрошена (например когда страница будет перезагружена), сервис-воркер запустится на этой странице и начнёт контролировать её. Когда это произойдёт, событие <code>install</code> будет вызвано в сервис-воркере; вы можете написать код внутри сервис-воркера, который будет вызван в процессе установки.</p>

<p>Давайте взглянем на файл сервис-воркера <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js">sw.js</a>. Вы можете видеть, что обработчик события <code>install</code> зарегистрирован на <code>self</code>. Ключевое слово <code>self</code> это способ ссылки на глобальную область видимости сервис-воркера из файла с сервис-воркером.</p>

<p>Внутри обработчика <code>install</code> мы используем метод {{domxref("ExtendableEvent.waitUntil()")}}, доступном в объекте события, чтобы сигнализировать, что работа продолжается, и браузер не должен завершать установку, пока все задачи внутри блока не будут выполнены.</p>

<p>Здесь мы видим Cache API в действии. Мы используем метод {{domxref("CacheStorage.open()")}} для открытия нового объекта кеша, в котором ответы могут быть сохранены (похоже на объект хранилища IndexedDB). Промис выполнится с объектом {{domxref("Cache")}}, представляющим собой кеш <code>video-store</code> . Затем мы используем метод {{domxref("Cache.addAll()")}} для получения ресурсов и добавления ответов в кеш.</p>

<pre class="brush: js">self.addEventListener('install', function(e) {
 e.waitUntil(
   caches.open('video-store').then(function(cache) {
     return cache.addAll([
       '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/',
       '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html',
       '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js',
       '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css'
     ]);
   })
 );
});</pre>

<p>На этом установка завершена.</p>

<h4 id="Отвечаем_на_последующие_запросы">Отвечаем на последующие запросы</h4>

<p>Когда сервис-воркер зарегистрирован и установлен на странице HTML и сопутствующие ресурсы добавлены в кеш, все практически готово. Нужно сделать ещё одну вещь - написать код для ответа на дальнейшие сетевые запросы.</p>

<p>Это то, что делает вторая часть кода файла <code>sw.js</code>. Мы добавили ещё один обработчик к сервис-воркеру в глобальной области видимости, который запускает функцию-обработчик при событии <code>fetch</code>. Это происходит всякий раз, когда браузер делает запрос ресурса в директорию, где зарегистрирован сервис-воркер.</p>

<p>Внутри обработчика, мы сначала выводим в консоль URL запрашиваемого ресурса. Затем отдаём особый ответ на запрос, используя метод {{domxref("FetchEvent.respondWith()")}}.</p>

<p>Внутри блока мы используем {{domxref("CacheStorage.match()")}} чтобы проверить, можно ли найти соответствующий запрос (т.е. совпадение по URL) в кеше. Промис возвращает найденный ответ или <code>undefined</code>, если ничего не нашлось.</p>

<p>Если совпадение нашлось, то просто возвращаем его как особый ответ. В противном случае, используем <a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a> для запроса ресурса из сети.</p>

<pre class="brush: js">self.addEventListener('fetch', function(e) {
  console.log(e.request.url);
  e.respondWith(
    caches.match(e.request).then(function(response) {
      return response || fetch(e.request);
    })
  );
});</pre>

<p>На этом все для нашего простого сервис-воркера. Используя подобный метод, вы можете сделать гораздо больше вещей — для получения доп. информации смотрите <a href="https://serviceworke.rs/">рецепты использования сервис-воркеров</a>. Спасибо Paul Kinlan за его статью <a href="https://developers.google.com/web/fundamentals/codelabs/offline/">Adding a Service Worker and Offline into your Web App</a>, которая вдохновила на написание данного примера.</p>

<h4 id="Тестируем_наш_пример_офлайн">Тестируем наш пример офлайн</h4>

<p>Для тестирования <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/">примера</a>, вам нужно загрузить его несколько раз, чтобы быть уверенным, что сервис-воркер точно установлен. Когда это сделано, вы можете:</p>

<ul>
 <li>отключиться от сетевого соединения.</li>
 <li>нажмите <em>Файл &gt; Перейти в офлайн, </em>если вы используете<em> </em>Firefox.</li>
 <li>перейдите в инструменты разработчика, выберите <em>Application &gt; Service Workers</em>, нажмите галочку <em>Offline</em>, если используете Chrome.</li>
</ul>

<p>Если обновите страницу с примером снова, вы увидите, что все работает как обычно. Все данные хранятся в офлайн хранилище — ресурсы страницы в кеше, а видео в базе данных IndexedDB.</p>

<h2 id="Итого">Итого</h2>

<p>Это всё, пока что. Мы надеемся наш краткий обзор <code>client-side storage</code> окажется полезным для вас.</p>

<h2 id="Также_стоит_почитать">Также стоит почитать</h2>

<ul>
 <li><a href="/en-US/docs/Web/API/Web_Storage_API">Web storage API</a></li>
 <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a></li>
 <li><a href="/en-US/docs/Web/HTTP/Cookies">Cookies</a></li>
 <li><a href="/en-US/docs/Web/API/Service_Worker_API">Service worker API</a></li>
</ul>

<p>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</p>

<h2 id="В_этом_модуле">В этом модуле</h2>

<ul>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Introduction to web APIs</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">Third party APIs</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">Video and audio APIs</a></li>
 <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">Client-side storage</a></li>
</ul>