【JavaScript】なぜObjectはメソッドを持たないのか

 JavaScriptのArrayには、forEachやmapなどの様々なメソッドが存在します。同じような使い方をされることの多いObjectに同様のメソッドを備えていないことに違和感を持つ方もおられるかと思います。

 今回は、なぜObjectがメソッドを持たないのか、という理由について考えてみます。

なぜObjectはメソッドを持たないのか

 なぜObjectはforEachやmapなど、Arrayと同じように使える便利なメソッドを持たないのでしょうか。

 これは察するに、JavaのHashMapなどと違い、Objectへのアクセスに専用のメソッドを持たない(常にオープンである)ことが関連しています。即ち、以下のような衝突を防止すべく、最低限のメソッドしか備えていないことと推察されます。

let obj = {};

obj["forEach"] = true;
obj["map"] = false;

 ご存知のとおり、たとえばObject.keys()やObject.seal()などのメソッドはstaticメソッドになっています。これは名称が衝突すると非常に深刻な問題を引き起こす可能性があるため、普遍的なメソッド名を持つメソッドは、staticな(つまり、Object.xx())メソッドに委譲したのでしょう。

 となれば、「Objectのprototypeにメソッドを生やす」などということは、非常に危険であることはお分かり頂けると思います。

Object.prototype.map = function(fn){
  return Object.keys(this).map((key)=>{
    var val = this[key];
    return fn.call(self,key,val);
  });
};

var a = {"a":"a", "b":"b"};
console.log(a.map(val=>{ return val + 1; }));  // -> ["a1", "b1"]

 このようにすれば、もちろんプロトタイプメソッドとして動作しますが…。仕様変更等で、「オブジェクトのプロパティとして、地図に関するプロパティを定義しなければならなくなった」というケースでは、だれかが以下のような改修をするかもしれません。

var a = {"a":"a", "map":{x:40, y:40}};
console.log(a.map(val=>{ return val + 1; }));  // -> Uncaught TypeError: a.map is not a function

 こうなってしまうと、悲劇が起こってしまいます。

ビルトインオブジェクトのprototypeは触らないようにする

 そもそもの話、ビルトインオブジェクトのprototypeを直接弄ってしまうようなことは、非常に「お行儀の悪いこと」です。APIドキュメントを充実させるなどして使うことはできますが、そうするなら独自にオブジェクト(クラス)もしくはオブジェクトを引数にとる汎用的なメソッドを作成してしまったほうがよいでしょう。

 使い方を誤らなければ便利なのですが、ソースコードの見通しが悪くなってしまうことが非常に多いです。好き勝手にprototypeメソッドを生やしてしまうようなことは絶対に避けるべきです。

より適したオブジェクトを使うようにする

 ES6からは、Mapをはじめとした数々の有益なクラスが用意されています。

 Mapは、今まで歴史的な理由からObjectが連想配列として使われてきたことに鑑み、ES6から新たに実装されたものです。JavaやC#などのMap(Collection)クラスとよく似た使い方ができます。

let map = new Map();
map.set("a", "aaa");
map.set("b", "bbb");
map.set("c", "ccc");

map.forEach(value=>console.log(value));  // -> aaa bbb ccc

 forEachなども標準で備えており、大変便利になりました。主要なモダンブラウザは機能として備えているようですが、IEなどは一部未実装のメソッドがあるようです。

まとめ

 ObjectにforEachがあれば…、などということは、おそらく誰でも考えていることでしょう。

 ではなぜ実装されていないのか?問題意識をもって考えてみることで、そこには意外な理由が見つかるかもしれません。