ローカルストレージの中身 ── 見えるのはこのサイトのぶんだけ
{"from":"potamo.net","ためし":true,"かず":1}
もとは { from: "potamo.net", ためし: true, かず: 1 } という組です。 保存できるのは文字列だけなので、JSON.stringify で文字に直してから渡しています。直さずに渡すと、中身が丸ごと消えて別のものになります。
画面がついたら、この機器に保存されているものを読みます。読むのはこの機器の中だけで、どこにも送りません。
画面がついたら読みます。
読んだ中身はこの画面に出しているだけです。 ただし人に見せている画面では、ここに並ぶ中身がそのまま見られることに気をつけてください。
よくある質問
ほかのサイトが保存したものも見られますか?
見られません。1つも出てきません。ブラウザは保存する場所をオリジン(通信のしかた・ホスト名・ポート番号の組)ごとに分けていて、別のオリジンのページからはのぞけないようにしています。ですから「このブラウザに入っているものを全部見せます」というページは、作り方が悪いのではなく仕組みとして作れません。ここに出るのは potamo.net が保存したものだけです。
開いたら空でした。壊れているのですか?
壊れていません。空であることが正しい答えです。potamo.net は、あなたが「試しに1つ書く」を押さないかぎり何も保存しないからです。空の画面を見て「この道具は動いていない」と思われがちですが、実際には、このサイトが何も預かっていないことをそのまま映しています。押せば1組増えるので、動いていることは自分で確かめられます。
ローカルストレージに保存したものは、ずっと残りますか?
残るとは限りません。とくに Safari は、そのサイトを触らないまま Safari を7日ぶん使うと、そのサイトが書いたものをまとめて消します。消える対象には LocalStorage も SessionStorage も IndexedDB も入ります。ですから「保存しました」と言い切る作りにはせず、消えても困らないものだけを置くのが安全です。
localStorage と sessionStorage は何が違いますか?
消えるときと、どこまで中身を共有するかが違います。localStorage は期限が無く、同じオリジンならどのタブで開いても同じ中身です。sessionStorage はタブごとに別で、そのタブを閉じると消えます。同じページを2つのタブで開くと、sessionStorage の中身は別々になります。どちらも入るのは文字列だけで、サーバーへ自動で送られることはありません。
数値や true をそのまま保存できますか?
できません。渡したものは文字列に直されてから保存されます。数の1は文字の「1」に、true は文字の「true」になります。いちばん引っかかるのは null で、これは4文字の「null」になります。取り出すときの getItem は「そこに何も無い」ときにも null を返すので、保存した null と、そもそも無いことが見分けられなくなります。
どれくらいの量まで保存できますか?
目安は1つのオリジンあたり5メビバイトほどですが、ブラウザや設定によって違うので言い切れません。文字数に直すとおよそ2,621,440文字ぶんです。上限をこえて保存しようとすると QuotaExceededError という例外になり、書き込みは行われません。ほかのサイトのぶんを消しても、このサイトの上限が増えることはありません。
このページに出た中身は、どこかに送られますか?
送っていません。読み込みも書き込みも、あなたの機器の中だけで起きています。このページには入力欄そのものがなく、画面に出しているのは、あなたのブラウザが持っている中身をその場で読んだものです。ただし人に見せている画面では、その中身がそのまま見られることには気をつけてください。
ここに出るのは potamo.net が保存したものだけです。 ほかのサイトが保存したものは1つも見えません。 ブラウザが保存する場所をサイトごとに分けているからで、 この道具の出来ではなく、ウェブの決まりごとです。
「このブラウザの中身を全部見る」ページは、作れません
ブラウザは、保存する場所をオリジンごとに分けています。 オリジンとは通信のしかた・ホスト名・ポート番号の3つの組で、 1つでも違えば別の場所になります。 別のオリジンのページからは、のぞくことも書き換えることもできません。
面白いのは、同じホスト名でも分かれるところです。 MDN は、http で読み込んだページと https で読み込んだページでは、返ってくる保存場所そのものが別のものになると書いています。 住所の文字が同じでも、鍵のかかった別の部屋というわけです。
ですから「ブラウザに溜まっているものを全部一覧します」という道具は、手を抜いているから作れないのではなく、原理として作れません。 検索で上に出てくるのが、どれも開発者ツールの開き方を説明した記事ばかりなのはそのためです。 ブラウザ自身の道具なら全部のオリジンをまたいで見られますが、ページの中で動く JavaScript には、その権限がありません。
この仕切りは同一オリジンポリシーと呼ばれます。 ふだん意識することはありませんが、これが無ければ、 たまたま開いた見知らぬページから、 銀行のサイトが保存したものを読めてしまうことになります。この画面がほとんど空なのは、その仕切りが効いている証拠です。
Safari は7日で消します ──「保存しました」と言い切れない理由
名前が localStorage なのでずっと残るように聞こえますが、そうではありません。 WebKit(Safari の中身を作っているところ)の公式ブログは、 追跡を防ぐしくみの説明の中で、はっきりこう書いています。
deleting all of a website's script-writable storage after seven days of Safari use without user interaction on the site「そのサイトを触らないまま Safari を7日ぶん使ったあと、そのサイトがスクリプトから書いた保存領域を全部消す」。 消える対象として挙げられているのは、IndexedDB・LocalStorage・Media keys・SessionStorage・Service Worker の登録とキャッシュの5つです。
「7日たったら消える」ではありません
ここは取り違えやすいところです。数えているのはカレンダーの7日ではなく「Safari を使った7日」で、 しかもそのあいだ、そのサイトを触っていなければという条件が付きます。 Safari を1週間まったく開かなければ、その日数は数に入りません。 逆に毎日 Safari を使っていても、そのサイトを開かなければ7日目に消えます。
MDN も同じ規則を、消える側から言い直しています。
If an origin has no user interaction, such as click or tap, in the last seven days of browser use, its data created from script will be deleted.「そのオリジンに、ブラウザを使った直近7日のあいだクリックやタップが1度も無ければ、スクリプトから作られたデータは削除される」。 ブログはもう1つ、こうも書き分けています ── ホーム画面に追加した Web App は Safari とは別に数える。そのアプリを使えば数え直しになる。
逆になるところ ── Cookie のほうが残ります
ここで立場が入れ替わります。サーバーが Set-Cookie で配った Cookie は、この追い出しの対象から外れているのです。 つまりスクリプトから書いたものだけが消える規則で、 名前のうえでは「ずっと残る」ほうの localStorage が、期限つきの Cookie より先に消えることがあるということです。
持ち帰りは1つです。ブラウザに置いたものは、いつか消える前提で作る。消えて困るものは自分の手元か、あなたが選んで預けた先に置いてください。 このページが書く1組も、消えて困らないものだけにしてあります。
入るのは文字列だけです ── null が4文字になります
保存できるのは文字列だけです。名前も中身も文字列で、 数も配列も、そのままの形では入りません。 では文字列でないものを渡すとどうなるかというと、黙って文字列に直されます。断られるほうがまだ親切なくらいです。
| 渡すもの | 保存される文字列 | 何が起きているか |
|---|---|---|
| 1 | 1 | 数は見た目どおりの文字になる。ただし読むと文字列なので、足すとつながる |
| true | true | 真偽値も文字になる。読んだ値を if に入れると、false でも「あり」になる |
| null | null | 4文字の null になる。getItem は無いときにも null を返すので、見分けがつかなくなる |
| { a: 1 } | [object Object] | 中身が丸ごと消える。これが JSON.stringify を通す理由 |
| JSON.stringify({ a: 1 }) | {"a":1} | 文字にしてから渡すと中身が残る。読んだあと JSON.parse でもとに戻せる |
いちばん引っかかるのがnullです。 保存すると4文字の「null」になりますが、取り出すときの getItem は「そこに何も無い」ときにも null を返します。 つまり保存した null と、そもそも無いことが見分けられなくなるわけです。 空の文字列を保存したときは「空である」とちゃんと区別できるので、入れるなら null より空文字のほうが安全です。
そこで、まとまったものを入れたいときはJSON.stringify で文字列に直してから渡し、 取り出したら JSON.parse で戻します。 上の実験でこのページが書いているのも、その形です。 直さずにオブジェクトをそのまま渡すと、中身が丸ごと消えて「[object Object]」という15文字だけが残ります。
localStorage と sessionStorage は、消えるときが違います
よく並べて名前が出る2つですが、違うのは主に「いつ消えるか」です。 使い方も入るものも同じで、どちらも文字列だけを預かります。
| くらべるところ | localStorage | sessionStorage |
|---|---|---|
| いつ消えるか | 期限が無い。自分で消すか、ブラウザが消すまで残る | そのタブを閉じると消える |
| どこまで同じ中身か | 同じオリジンなら、どのタブで開いても同じ中身 | タブごとに別。同じページを2つのタブで開くと、中身も別 |
| 入れられるもの | 文字列だけ。名前も中身も文字列 | 文字列だけ。名前も中身も文字列 |
| 大きさの目安 | 1オリジンあたり5メビバイト | 1オリジンあたり5メビバイト(合わせて10メビバイト) |
| プライベートな表示のとき | sessionStorage と同じ扱いになり、閉じると消える | 同じく、閉じると消える |
| サーバーへ送られるか | 送られない。ページの中の JavaScript が読んだときだけ動く | 送られない。同じく、読んだときだけ動く |
表のいちばん下が大事なところです。どちらもサーバーへは送られません。ページの中の JavaScript が読みに行ったときだけ動きます。 ここが Cookie との一番大きい違いで、 知らないと「Cookie と同じようなもの」と覚えてしまいます。
大きさの数え方 ──「あ」も「a」も同じ2バイトです
上の一覧に出している「おおよそのバイト数」は、UTF-16 のひと区切りを2バイトとして数えたものです。 MDN が「保存される名前も中身も UTF-16 という形の文字列だ」と書いていて、 そのひと区切りは決まりのうえで2バイトだからです。 ふだん使う英数字も、ひらがなも漢字も、この区切り1つに収まります。
ここに面白い逆転があります。ふだん文字をファイルに書くときの形(UTF-8)だと、「a」は1バイト・「あ」は3バイトで3倍の差がつきます。 ところがこの保存場所の数え方では、「a」も「あ」も同じ2バイトです。 日本語のほうが不利にならない、というより、英数字だけの文が損をしていると言ったほうが近い形です。
絵文字はもう少し込み入っていて、人が1文字と感じるものが2区切りぶん=4バイトになります。 だから上の一覧では「文字数」と「符号単位」を別々に出しています。片方だけ見せると、数が合わないように見えるからです。
上限はブラウザによって違うので、言い切れません
目安は1つのオリジンあたり5メビバイトほどです。 文字数に直すとおよそ2,621,440文字ぶん(5メビバイト ÷ 2バイト)で、 原稿用紙にすると6千枚をこえます。ふだんの使い方では、まず届きません。
ただしこれは目安で、決まった数ではありません。 MDN 自身が、同じ説明の中で localStorage と sessionStorage を合わせた10メビバイトという言い方もしています。 ブラウザや設定によって違うので、このページでも「目安」と書くだけにとどめています。入りきらなければ QuotaExceededError という例外になります。 HTML Standard は「新しい値を入れられなかったときは例外を投げなければならない」と書いていて、そのときはその1組が書かれません── 入れたつもりで消えている、ということにはなりません。
引用と数値の出どころ: 7日で消える規則はWebKit Blog「Full Third-Party Cookie Blocking and More」(2020年3月24日)(新しいタブで開きます)、その言い直しと SecurityError・UTF-16 の話はMDN の Window.localStorage(新しいタブで開きます)、2つの保存場所の違いはMDN の Web Storage API(新しいタブで開きます)、大きさの目安はMDN の Storage quotas and eviction criteria(新しいタブで開きます)、文字列に直されることと例外の決まりはHTML Standard の Web storage(新しいタブで開きます)から取っています。英語の引用は原文をそのまま載せ、 日本語はその意味を変えないように訳したものです。