プリミティブ値でもプロトタイプ的継承 ― 2006年10月18日 23時50分
書き上げた後に元記事の続きが出ているのに気づいたが、方向性が違うようなのでそのまま掲載。
404 Blog Not Found:javascript - プロトタイプ的継承 (元記事: Prototypal Inheritance) より。継承という言葉は意味が広いので、この操作に対してはチャイルドの作成といったほうが個人的にはわかりやすい。
さて、元記事で紹介されているコードではプリミティブ値からのチャイルドの作成 (継承) ができなかった。これはなぜかといえば、オブジェクト作成の際、プリミティブ値をプロトタイプ ([[Prototype]] 内部プロパティ、__proto__
プロパティ) に設定することはできないからである。
そこで、プリミティブ値が渡された場合は、それをラッパオブジェクトに変換することにする。といっても場合分けの必要はない。Object
関数を使えば、プリミティブ値が渡されたときは対応するラッパオブジェクトを、オブジェクトが渡されたときはそれをそのまま返してくれる。
function object(o) {
function F() {}
F.prototype = Object(o);
return new F();
}
var myStr = object("子");
myStr.x = function (n) {
var s = "";
while (n--) s += this;
return s;
};
print(myStr.x(12)); // doesn't work as expected
ところが、このままでは期待通りには動かない。JScript では空文字列が出力されるし、JavaScript (SpiderMonkey) では例外 (TypeError: String.prototype.valueOf called on incompatible Object) が投げられる。
その理由は、Kazuho@Cybozu Labs: JavaScript の String 型を継承するでも触れられているとおり、String#toString
および String#valueOf
が String 型以外のオブジェクト上では (そのオブジェクトのプロトタイプが String 型のオブジェクトであったとしても) 呼び出せないからだ。ECMAScript 仕様ではこのことを not generic
(汎用的ではない) と表現している。
これは何も String 型に限った話ではない。Array#toString
も汎用的ではなく、実際 JScript では以下のように文字列に変換しようとすると例外が投げられる。
String(object([1, 2, 3]));
// Microsoft JScript 実行時エラー: Array オブジェクトがありません。
対応策として考えられるのが、汎用的でないメソッドが呼び出されたときには処理をプロトタイプに委譲してしまうことだ。一部の実装では __proto__
プロパティからプロトタイプにアクセスできるので以下のように書ける。
var myStr = object("子");
myStr.valueOf = function () {
return this.__proto__.valueOf();
};
myStr.x = function (n) { ... };
print(myStr.x(12)); // 子子子子子子子子子子子子
しかし、一般に ECMAScript ではオブジェクトのプロトタイプにアクセスする手段が提供されていない。そこで、チャイルドを作成する際に汎用的でないメソッドを上書きすることにする。
function object(o) {
o = Object(o);
function F() {}
F.prototype = o;
var obj = new F();
var methods = ["toString", "toLocaleString", "valueOf"];
for (var i = 0; i < methods.length; i++)
with ({ method: methods[i] })
obj[method] = function () { return o[method].apply(o, arguments); };
return obj;
}
var myStr = object("子");
myStr.x = function (n) { ... };
print(myStr.x(12)); // 子子子子子子子子子子子子
これでプリミティブ値からもチャイルドを作成できるようになった。とはいえ、このままでは Number#toFixed
や Date#getTime
などは使えないが、それらにも対応したものを prototypal.js として置いておく。
最近のコメント