おさらい: prototype
JavaScript のオブジェクトはみんな prototype というのを持っていて, この prototype からプロパティを継承, より正確には, プロパティアクセス時にそのプロパティがオブジェクトに存在しなければ prototype を辿って見つけにいくことになっている.
あるオブジェクトを prototype とした別のオブジェクトを作るには Object.create
を使う (あるいは new
演算子や __proto__
を使っても良い).
const x = {}; x.foo = "foo"; const y = Object.create(x); y.bar = "bar"; const z = Object.create(y); z.baz = "baz"; console.log(z.foo); // => "foo" console.log(z.bar); // => "bar" console.log(z.baz); // => "baz"
逆に, あるオブジェクトの prototype を取得するには Object.getPrototypeOf
を使う.
console.log(Object.getPrototypeOf(z) === y); // => true
ところで {}
のような「空」のオブジェクトにも prototype が存在して, これは Object.prototype
と呼ばれる.
この Object.prototype
には toString
のようなプロパティが定義されているので, 空のオブジェクトは真に空というわけではない.
const x = {}; console.log(Object.getPrototypeOf(x) === Object.prototype); // => true console.log(x.toString); // => [Function: toString]
では真に空のオブジェクトは作れないのかというとそんなことはなくて, prototype が null
のオブジェクトを作れば良い.
例えば Object.create(null)
などとする (ブログタイトル回収).
const w = Object.create(null); console.log(Object.getPrototypeOf(w)); // => null console.log(w.toString); // => undefined
Object.groupBy
Object.groupBy
は ES2024 で追加されたメソッドで, 配列などの iterable をグループ化して, それらのグループをプロパティに持ったオブジェクトを作成できる.
const arr = [3, 1, 4, 1, 5, 9, 2] const obj = Object.groupBy(arr, (e) => e % 2 === 0 ? "even" : "odd"); console.log(obj); // => { odd: [3, 1, 1, 5, 9], even: [4, 2] }
そして記事のタイトルの通り, Object.groupBy
で作られるオブジェクトの prototype は null
になっている.
console.log(Object.getPrototypeOf(obj)); // => null
このメソッドを追加する proposal の README を読むと, これには prototype から継承したプロパティと Object.groupBy
によって作られたプロパティが混ざって困ったことが起こらないように, という意図があるようだ.
returns a null-prototype object, which allows ergonomic destructuring and prevents accidental collisions with global Object properties
https://github.com/tc39/proposal-array-grouping?tab=readme-ov-file#motivation
TypeScript と null-prototype オブジェクト
ところで TypeScript には prototype が null
のオブジェクトを表す型はない.
空のオブジェクト型 {}
がそれに該当すると思われるかもしれないが, 実は全てのオブジェクト型で toString
などの Object.prototype
の持つプロパティは暗黙的に継承されていることになっていて, プロパティアクセスが行えてしまう.
つまり以下のようなコードは型検査を通過するが, 実行してみるとエラーが発生する.
const obj = Object.groupBy([], () => "*"); obj.toString(); // => TypeError: w.toString is not a function
これまで prototype が null
のオブジェクトが登場するような状況は (私の知る限り) かなり限られていたのであまり気にならなかったのだが, 今後は Object.groupBy
の普及に伴ってしばしば登場するかもしれないので注意が必要そうである.
そもそもオブジェクトを辞書のように用いる (index signature を使う) のはいくらか型安全性に問題があるため, 多くの場合で Object.groupBy
よりも Map.groupBy
を使う方が適しているだろうということは覚えておきたい.
また誤ったメソッドの呼び出しがもしあれば気がつけるように, ESLint の no-prototype-builtins や no-base-to-string といったルールも有効化しておくと良い.