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

JavaScriptのprototype拡張いろいろ

こんばんは、south37です。毎回ギリギリになって気づいて慌ててる系なんですが、今日も元気に書いていきたいと思います。今回は前回に引き続き、JavaScript-The-Good-Partsの中に出てくるprototype拡張について説明していきたいと思います。

jsにおけるFunctionオブジェクト

jsの関数はみなオブジェクトで、Functionオブジェクトと呼ばれます。そして、Function.prototypeへのリンクをみな持っています。つまりFunction.prototypeを書き換えればjs上の全ての関数に影響を与えられるという訳ですね。で、good partsではFunction.prototypeももちろん書き換えちゃいます。

Function.prototypeの拡張としては、しょっぱなでmethodメソッドというのが定義されています。コードだと、

Function.prototype.method = function (name, func) {
  this.prototype[name] = func;
  return this;
};

って感じです。このメソッドは、jsの標準型を拡張したい時に使います。前回の記事で書いたように、jsではコンストラクタはただの関数オブジェクトです。そのため、method関数をメソッドとして使えるようになる訳です。

jsの標準型である「数字」や「文字列」も背後では関数オブジェクトをコンストラクタとして用いています。だから、例えばある特定の数字の整数部分だけを取り出したいとき、

Number.method('integer', function () {
  return Math[this < 0 ? 'ceil' : 'floor'](this);
});

みたいなコードでintegerメソッドを「数字(Number)」型に追加しておくと、

console.log( (-10 / 3).integer() ); //-3

のように数字に対してinteger()を呼び出せるようになります。こういった柔軟性は、取り扱いは難しいですが僕は好きですね。

ちなみに、integerメソッドを追加してる部分はちょっとトリッキーなコードになってます。というのも、Math.ceilやMath.floorと書かずに、あえてMath[ceil]の形の呼び出しにし、かつ[ ]のなかで三項演算子を用いています。さらに、引数としてthisを渡してます。個人的には、下手にコードを短くしようとせずにif分岐で書いた方がパッと見分かり易いと思います....

あと、prototypeとは離れるんですが、good partsではnew 演算子を使わないオブジェクト生成を勧めていたりします。コンストラクタではない関数に、オブジェクトを返させるのです。例として、前回の記事で紹介したQuoと同じオブジェクトを返すものとして、quoというメソッドを定義してみます。

var quo = function (status) {
  return {
    get_status: function () {
      return status;
    }
  };
};

var myQuo = quo("amazed");
console.log(myQuo.get_status()); //amazed

みたいな感じです。ただ、オブジェクトをその場で作ってreturnしてるだけです。まんまなんですが、一つ特徴としてはstatusという変数に外部からはget_statusメソッドを用いてしかアクセス出来なくなっています。カプセル化が出来ている訳ですね。これは、jsのスコープが自分より外側の変数にもアクセス出来るようになっている為に出来る事で、こういった性質をさしてクロージャと呼んだりします。

上の例では、引数として受け取ったstatusへの参照をget_statusが持っていて、その状態でオブジェクトがreturnされています。その為、statusへはget_statusからはアクセス出来る状態となり、かつもはやstatusという変数は存在しない為それ以外のアクセス方法は無くなる訳です。なかなか面白いですね。

Functionオブジェクトについてはこんなもんでいいでしょう。

jsにおける疑似クラス

jsにおける継承(っぽいもの)は、コンストラクタのprototypeに継承したいオブジェクトを設定してからnewしてやる事で実現出来ます。ただ、あんまりにもアレなコードになっちゃうという事で、inheritsメソッドというのを定義してやります。

Function.method('inherits', function (Parent) {
  this.prototype = new Parent();
  return this;
}

見ての通りで、中で生成したオブジェクトをprototypeに設定してるだけです。しかし、一応継承っぽい動きは出来ます。また、

var Mammal = function (name) {
  this.name = name;
};
Mammal.prototype.get_name = function () {
  return this.name;
};
Mammal.prototype.says = function () {
  return this.saying || '';
};

var Cat = function (name) {
  this.name  = name;
  this.saying = 'meow';
}.inherits(Mammal);

みたいに、コンストラクタの定義に続いて呼び出してちょっとオシャレっぽい感じに書けます。

ただ、good partsのこの章は「new演算子はクソなのでコンストラクタ自体使わない方がいい」という言葉で締められてました。ここまで説明しといてなんじゃそれと思います。

で、代わりにオススメされていたのは「関数型コンストラクタ」によるオブジェクト生成です。クロージャを利用したカプセル化なんかも出来るようにしてて、いい感じのものらしいです。「関数型コンストラクタ」についての説明は、次回にしたいと思います。

追記

このページによれば、new演算子使うなってのはぶっとんだ主張らしいです。確かに、backboneみたいな現代的なフレームワークはnewによるオブジェクト生成を当たり前のように行ってて、どうなってるんだろうと思ってました。皆さんも心配せずにnew演算子を使いましょう。後、Effective JavaScriptはいい本らしいのでいつか読んでみたいと思います。ただ、高いので買ってくれると嬉しいです。