◆ 普通に使ってると気づかない部分
◆ Uint8Array の subarray は同じ ArrayBuffer の一部
  ◆ byteOffset と byteLength で開始位置と長さをもってる
  ◆ buffer は元の ArrayBuffer
◆ slice は普通の配列と同じようにコピー
◆ Node.js の Buffer だと slice でも subarray と同じ動き
◆ Buffer.from で作ると pool の ArrayBuffer が使われて その subarray になる
  ◆ Buffer.of や Buffer.alloc だと pool を使わない新しい ArrayBuffer
  ◆ Uint8Array.from も新しい ArrayBuffer

Uint8Array の subarray と slice

subarray は名前通り 元の配列の部分配列なので subarray の一部を書き換えると元も変わります

const u = new Uint8Array([1, 2, 3, 4])
const s = u.subarray(1, 3)
s[0] = 10
console.log(u, s)
// Uint8Array(4) [1, 10, 3, 4] Uint8Array(2) [10, 3]

slice の場合は普通の配列と同じでコピーされてるので書き換えても元は変わりません

const u = new Uint8Array([1, 2, 3, 4])
const s = u.slice(1, 3)
s[0] = 10
console.log(u, s)
// Uint8Array(4) [1, 2, 3, 4] Uint8Array(2) [10, 3]

普通の配列はもちろんこうなります

const a = [1, 2, 3, 4]
const s = a.slice(1, 3)
s[0] = 10
console.log(a, s)
// (4) [1, 2, 3, 4] (2) [10, 3]

Node.js でもこれは同じです
ですが Node.js の Buffer を使った場合は slice は subarray と同じ動きになります
slice でも元が変わります

const b = Buffer.from([1, 2, 3, 4])
const s = b.subarray(1, 3)
s[0] = 10
console.log(b, s)
// <Buffer 01 0a 03 04> <Buffer 0a 03>

const b = Buffer.from([1, 2, 3, 4])
const s = b.slice(1, 3)
s[0] = 10
console.log(b, s)
// <Buffer 01 0a 03 04> <Buffer 0a 03>

subarray

subarray は元の ArrayBuffer に対する開始地点と長さを持っています
byteOffset と byteLength プロパティで取得できます
buffer プロパティの ArrayBuffer は元の Uint8Array と同じものです

const u = new Uint8Array([1, 2, 3, 4, 5, 6])

const s = u.subarray(2, 5)
console.log(s.buffer)
// ArrayBuffer { [Uint8Contents]: <01 02 03 04 05 06>, byteLength: 6 }
console.log(u.buffer === s.buffer)
// true
console.log(s.byteOffset, s.byteLength)
// 2 3

const t = u.slice(2, 5)
console.log(t.buffer)
// ArrayBuffer { [Uint8Contents]: <03 04 05>, byteLength: 3 }
console.log(t.byteOffset, t.byteLength)
// 0 3

Buffer と ArrayBuffer

Buffer から ArrayBuffer を取得すると from で作った場合は pool が使われます

const f = Buffer.from([1, 2, 3, 4])
const o = Buffer.of(1, 2, 3, 4)

console.log(f)
// <Buffer 01 02 03 04>
console.log(o)
// <Buffer 01 02 03 04>

console.log(f.buffer)
// ArrayBuffer {
// [Uint8Contents]: <01 02 03 04 00 00 ...>,
// byteLength: 8192
// }
console.log(o.buffer)
// ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }

ArrayBuffer は同じ pool なので参照が等しくなります

Buffer.from([1, 2, 3]).buffer === Buffer.from([255]).buffer
// true

使うごとに未初期化部分に割り当てられます

Buffer.from([0xa,0xb,0xc]).buffer
// ArrayBuffer {
// [Uint8Contents]: <0a 0b 0c 14 49 7f 00 00 c8 fe 1d 14 49 7f 00 00 70 8b 74 03 00 00 00 00 70 8b 74 03 00...>,
// byteLength: 8192
// }

Buffer.from([0xd,0xe]).buffer
// ArrayBuffer {
// [Uint8Contents]: <0a 0b 0c 14 49 7f 00 00 0d 0e 1d 14 49 7f 00 00 70 8b 74 03 00 00 00 00 70 8b 74 03 00...>,
// byteLength: 8192
// }

Buffer.from([1, 2, 3, 4]).buffer
// ArrayBuffer {
// [Uint8Contents]: <0a 0b 0c 14 49 7f 00 00 0d 0e 1d 14 49 7f 00 00 01 02 03 04 00 00 00 00 70 8b 74 03 00...>,
// byteLength: 8192
// }

pool の ArrayBuffer の subarray という扱いで pool のどの部分かは offset でわかります

Buffer.from([1, 2]).offset
// 0
Buffer.from([1, 2]).offset
// 8
Buffer.from([1, 2]).offset
// 16
Buffer.from([1, 2]).offset
// 24

pool じゃない ArrayBuffer を作る

pool を使わず期待する ArrayBuffer を作りたい場合はいくつか方法があります

1 つめはこれです

const poolslice = Buffer.from([1, 2, 3])
const buf = Buffer.alloc(poolslice.length)
poolslice.copy(buf)
buf.buffer
// ArrayBuffer { [Uint8Contents]: <01 02 03>, byteLength: 3 }

alloc だと pool を使わないので alloc で作った Buffer へコピーします


引数が許すサイズなら Buffer.of に ... で展開する方法もあります

const poolslice = Buffer.from([1, 2, 3])
Buffer.of(...poolslice).buffer
// ArrayBuffer { [Uint8Contents]: <01 02 03>, byteLength: 3 }


Uint8Array を使えば from でも pool が使われないので Buffer の代わりに Uint8Array を使うのが一番楽です

Uint8Array.from([1, 2, 3]).buffer
// ArrayBuffer { [Uint8Contents]: <01 02 03>, byteLength: 3 }