TD; LR;
JavaScriptのBlobのコンストラクラの第一引数について、仕様を理解せずつかうと上手くいかないこともある
Blob第一引数
MDNの記述では
array
反復可能オブジェクト、例えば Array などです。その中身が ArrayBuffer、
TypedArray、DataView、Blob、文字列などのオブジェクト、
またはそのようなオブジェクトの何れかが混合したもので、それが Blob の中に入れられます。
ここで文字列は UTF-8 で符号化されたものであり、 JavaScript におけるふつうの
UTF-16 の文字列ではありません。
本来は
const binary = new Uint8Array([11,22,33,44])
const blob = new Blob([binary]) // binaryが配列の中にある
と書いてほしいという記述なんですが、そもそもTypedArray(Uint8Arrayとかのこと)も反復可能オブジェクトですよね?そのままいれてはいけないんですか?って話です
const createLink = (blob:any, title:string, download:string) => {
// @ts-ignore
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.innerHTML = title
anchor.href = url
anchor.download = download
anchor.style.display = 'block'
return anchor
}
const main = async() => {
const numbers = [11,22,33,44,-100]
const binary = new Int8Array(numbers)
// Arrayの要素としてUint8Arrayをいれている
const blob = new Blob([binary])
// Uint8Arrayをそのまま第一引数にしている
// @ts-ignore
const blob2 = new Blob(binary)
// raw arrayを第一引数にしている
// @ts-ignore
const blobNumbers = new Blob(numbers)
const anchor0 = createLink(blob, 'blob correct', 'typedarray.raw')
const anchor1 = createLink(blob2, 'blob incorrect', 'typedarray-wrong.raw')
// @ts-ignore
const anchor2 = createLink(blobNumbers, 'array', 'blobarray.raw')
document.body.appendChild(anchor0)
document.body.appendChild(anchor1)
document.body.appendChild(anchor2)
}
document.addEventListener("DOMContentLoaded", (event:unknown) => {
main()
})
ダウンロードリンクを3つだしているわけですが、
- Arrayの要素にTypedArrayをいれたパターン
- Arrayの要素にせず直接Int8Arrayをつめたパターン
- 数値のArrayをつめたパターン
です
おのおのダウンロードすると以下のような「バイナリファイル」が保存されます
1つめ
0B16212C 9C
2つめ
31313232 33333434 2D313030
3つめ
31313232 33333434 2D313030
2つめと3つめはテキストエディタで開くと以下のとおりになります
11223344-100
なにがおきているのか?
Blobのコンストラクタの定義はECMA-262で定義されるWebIDLによれば以下の通り記述されるものです
[Constructor(optional sequence<BlobPart> blobParts,
optional BlobPropertyBag options),
Exposed=(Window,Worker), Serializable]
interface Blob {
...
typedef (BufferSource or Blob or USVString) BlobPart;
...
https://github.com/microsoft/TypeScript-DOM-lib-generator/blob/e2a2072d804999180f45c43f102d2ee604fade65/inputfiles/idl/File.widl
sequenceは型を取れるリストですが、こちらは長いので
https://triple-underscore.github.io/infra-ja.html#list
とかを読むといいです
そしてsequenceに取る型はBlobPartです
BlobPartはBlob,USVString,BufferSourceのいずれかです
ここで
type BufferSource = ArrayBufferView | ArrayBuffer;
ArrayBufferViewは以下を参照してください
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/isView
結局なにかというと文字列(USVStringは16bit表現の文字列)かArrayBuffer(View)か、Blobの配列がBlobのコンストラクタの第一引数でとることのできる型になり、ArrayBufferはそのままバイナリを、そうでないものは文字列表現に変換されBlobにおさめられるることとなります
ですので[TypedArray]で生成されたBlobではバイナリが、TypedArrayを直接指定するとSerializeされた文字列が保存されます
つまり[11,22,33,44,-100]はバリナリではなく文字列として11223344-100としてBlobに保存されることになります
Blobの第一引数がsequenceなのはなぜだろう?
複合的なデータを構築するためでしょう。テキストであれ、バイナリであれ、順列に構成していくことで多様なデータ形式に対応するためにこのような仕様になってると思います(根拠はない)
typescriptでは?
WebIDLから生成された制約が有効になるためサンプルのように無効化しない限りエラーになります
これを無視しない限り、変な問題には遭遇しないことが期待されます
第一引数はBlobPart[]になります(詳細は上述の通りです)
まとめ
Blobの第一引数についての謎をおいかけました
この内容を理解した上で、最初のMDNの記述を読むと「正しく」理解できると思います
雑感
調べると言語/API仕様のお気持みたいなものがわかる気がする