読者です 読者をやめる 読者になる 読者になる

JavaScript の名前空間になぜ Function Object をつくるんだろうって話

JS で名前空間分けられないから独自に $ であるとか OreOreModule とかで一度空間を作ってしまってそこでいろいろ定義するというのがありがちなパターンだけど、なんで Object じゃだめなんだろうって思ってちょっと調べた。

keyword

prototype, new, Object.create()

そもそも

var o = {}; で o には prototype がない。function (){} だとある。以上。ではあるが。

new してかく

JavaScript Good Parts 的には new 禁止的なあれらしいが、まあでも実際つかってるよね。なんか便利にやってるよね。じゃあ、new ってなにやってるんだろ?って話。

new しないと this キーワードが反映されない。this は呼び出し元を指し示す予約語。new しないでオブジェクトを作成すると this が window = グローバルになってしまって漏れてよくない。具体的な事例は上記参考をみるか自分でやってください。

具体的なコードをかく。コメントもかく。

// 元になるクラス的なもの
// 普段はからっぽの名前空間だったりする
var Man = function (name, age){
  this.name = name;
  this.age = age;
  this.say = function () {console.log(this.age, this.name);};
  // いちおう明示的に this をかえしておく
  return this;
};

// インスタンスをつくる感じ
// Man Object
var m = new Man('hoge', 22);
console.log(m.age); //22
console.log(m.name); // hoge
console.log(m.say()); // 22 'hoge'

return this; はかかなくてもよさそうだったけど、いちおう明示的にしておいた。で、this には値をいれることはできない。関数内で object を作成することもできるが、そうすると当然 prototype がとれない。__proto__ をつかえなくはないけど、それは独自プロパティであってIEだと動かないというか ECMAScript 標準仕様じゃないからまあ却下。

関係ないけど Object とか Array とか Function とか String そのものは関数だから prototype あるんだよね。

new しないでかこうとする方法

上記参考より。コメントもできるだけかいた。

// 名前空間をつくる
var Blog = function() {
  // 内部でさらにもういっこ Function Object をつくる
  var F = function() {};
  // その prototype は純粋(?)な Object らしい
  F.prototype = Blog.prototype;
  // で、それを内部で new してかえす
  return new F;
};

// prototype に生やす
Blog.prototype.service = 'はてなダイアリー';

// ここでは new しない
var blog = Blog();
console.log(blog.service); // はてなダイアリー

これでたしかに使う側では new しないでかけた。で、これをもうちょっと汎用的っぽい書き方をする。っていってもこれも参考とほぼ同じ。

// class は一応予約語で使えないので klass
var klass= function(obj) {
  var F = function() {};
  F.prototype = obj.prototype;
  return new F;
};

var Blog = {};
Blog.prototype.foo = 1;

var Twitter = {};
Twitter.prototype.foo = 2;

// klass 関数で インスタンスを作成する感じ
var blog = klass(Blog);
var twitter = klass(Twitter);
console.log(blog.foo) // 1
console.log(twitter.foo); // 2

klass(Obj) としたことでだいぶつかいやすい感じ。上に書いたが参考資料の名前変えただけだけど、だいぶしっくりくる。

Object.create() からはいるアプローチ

ECMAScript 5 から Object.create() がつかえるようになった。他には Object.defineProperty() とか*1。上記参考資料より。

// もとになる Object を作成
var man = {
  age: null,
  name: null,
  // こちらにもたせてもみたけど、あまり意味はない
  hello: function (){console.log(this.age, this.name);}
};

// セッターっぽいことになってしまったが、まあ
var setMan = function (name, age){
  // 関数内で Object.create して扱う
  var child = Object.create(man);
  // 各種プロパティとか
  child.name = name;
  child.age = age;
  // こちらにもたせてもみたけど、あまり意味はない
  child.say = function () {console.log(this.name, this.age);};
  // Object.create した結果を返す
  return child;
};

// 親に持たせるやりかた
var a = setMan('moge', 22);
a.hello(); // 22 'moge'
// 子に持たせるやりかた
var b = setMan('namela', 23);
b.say(); // namela 23

Object.create や F.prototype が「純粋なオブジェクト」というのがあまりよくわかっていないのだけど、こういうアプローチもとれる。

var child = Object.create(man) したとき、親となるのは man で、子となるのは child。

man と setMan() に両方関数をもたせるのもアレだなと思ったけど、まあ習作。this が呼び出し元のオブジェクトを指しているというのを理解していれば、特にハマることはない。モデルや振る舞いとして持たせるべきところにもたせてやればよい。

雑感

個人的には無理にそこまでして new さけなくてもいいような気がする。副作用がどういうのかがわからないのだけど。Object.create(obj) が ECMAScript 5 準拠なのはもうそろそろ IE9 以降の世界に住めるならいいけど、それでも new して return this; がわかりやすい気がするなー。

まあ

独自クラスシステムはそもそもどうなの、という話はおいておく。なんかそれどうよって思ったらそれとなく伝えてくれるとうれしいです。

ここまで書いたけど

でもよく考えたら、なんで prototype ではやしていくほうが JS 的にいいんだろう?別に var o = {} でプロパティに生やしていってもいいのでは?

書いた直後だけど

継承とプロトタイプチェーン - JavaScript | MDN
MDN にいろいろ書いてあったわ。プロトタイプチェーンの問題か。とはいうものの、クラスを多重に重ねていくやりかたはあまり想像できないのだけど。Sencha Touch(Ext JS) みたいなのを自前でつくるときに必要なのかな……。

*1:個人的にははやくリスト内包とジェネレーターが欲しいので ECMAScript 6 に期待している