【JavaScript】 createObjectURL()した後にrevokeObjectURL()が必要な理由
更新日:2020/07/27
URL.createObjectURL()を実行した後、URL.revokeObjectURL()を実行する必要があります。
という説明を見たのだけれど、理由がわからなかったので調べてみました。
オブジェクトURLとは
オブジェクトURLとは、オブジェクトに便宜的なユニークなIDを付け、そのIDとURLの種類を組み合わせた文字列です。
この文字列はブラウザがURLとして解釈でき、ブラウザがオブジェクトURLを管理する仕組みに渡すことで、対応するデータを取得します。
もう少し詳しく解説します。
オブジェクトURLの作成
File APIなどで取得したFileオブジェクトを、次のようにURL.createObjectURL()の引数に渡すと、オブジェクトURLを取得できます。
オブジェクトURLの取得
const objectUrl = URL.createObjectURL( obj );
※ObjはBlobオブジェクトを指定します。FileオブジェクトはBlobから派生しているので指定可能です。
取得したオブジェクトURLは、次のような"blob:"で始まる文字列です。
"blob:null/f79b06de-2072-4fe4-bb2b-a89383231a79"
後ろに続く文字列は、createObjectURL()が呼び出された時に他と重複しないように作成されます。
このときオブジェクトの同一確認をしていないので、同じオブジェクトでも呼び出すたびに異なるオブジェクトURLが作成されます。
オブジェクトURLの仕組み
Blobから作成したオブジェクトURLは、Blob URLストアにBlobオブジェクトへの参照とペアで格納されています。
Blob URLストア:{ Blob URL1:{ オブジェクトURL , Blobオブジェクトへの参照 } Blob URL2:{ オブジェクトURL , Blobオブジェクトへの参照 } ・・・・ }
URLの参照行為、例えばimgタグのsrcにオブジェクトURLがセットされると、Blob URLストアから一致するものを検索してBlobオブジェクトから、データを取得します。
オブジェクトURLはBlobを保持する
JavaScriptは他から参照されていないオブジェクトを、ガーベッジコレクションのアルゴリズムに基づいて自動でメモリ上から削除します。
逆に言うと、他から参照されている間はメモリ上に残ります。
File APIなどで作成されたBlobオブジェクトは、必要なくなれば削除されます。
しかしURL.createObjectURL()を実行すると、Blob URLストア内にBlobオブジェクトへの参照が作成されます。
これは自動で削除してくれません。
そのため、ガーベッジコレクションがメモリ上から削除してくれません。
つまり、使用する予定がないのに、いつまでも残ります。
Blobへの参照を削除する
Blobオブジェクトを削除対象にするには、URL.revokeObjectURL()を使用して、Blob URLストア内から参照を削除する必要があります。
オブジェクトURLの破棄
URL.revokeObjectURL( objectUrl );
revokeObjectURL()は、引数で与えられた文字列(オブジェクトURL)と一致するものを検索して、Blob URLストア内から削除します。
revokeObjectURL()を実行するタイミング
revokeObjectURL()は、URLを取得したオブジェクトが不必要になった時点で実行します。
例えばimgタグのソース読み込みでは、画像の読み込みが終了または失敗した時点で実行します。
createObjectURL()での画像読み込み
const img = document.createElement("img");
const imageURL = URL.createObjectURL( fileObj );
img.onload = ()=>{
URL.revokeObjectURL(imageURL);
// imgをDOMに追加するなどの処理
};
img.onerror = e=>{
URL.revokeObjectURL(imageURL);
};
img.src = imageURL;
ただし同じURLを共有する場合は、注意が必要です。
createObjectURL()での複数画像を読み込み:失敗パターン
const img1 = document.createElement("img");
const img2 = document.createElement("img");
const imageURL = URL.createObjectURL( fileObj );
img1.onload = img2.onload = e=>{
URL.revokeObjectURL( imageURL );
};
img1.onerror = img2.onerror = e=>{
URL.revokeObjectURL( imageURL );
};
img1.src = img2.src = imageURL;
上のコードは一方のimgタグしか画像が表示されません。
どのような順番でイベントが発生するかわかりませんが、例えば最初にimg1で読み込み完了イベントが発生したすると、その時点でオブジェクトURLが失われてしまうので、img2はデータを読み込むことができないからです。
上手く読み込ませるには、次の例のように URL.createObjectURL()を2回呼び出すなどの工夫が必要です。
createObjectURL()での複数画像を読み込み:改良パターン
const img1 = document.createElement("img");
const img2 = document.createElement("img");
img1.onload = img2.onload = e=>{
URL.revokeObjectURL( e.target.src );
};
img1.onerror = img2.onerror = e=>{
URL.revokeObjectURL( e.target.src );
};
img1.src = URL.createObjectURL( fileObj );
img2.src = URL.createObjectURL( fileObj );
更新日:2020/07/27
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。