JavaScriptでnew演算子をつけてもつけなくても同じようにインスタンスを作成の続き
またJavaScriptで遊んでる。前回→JavaScriptでnew演算子をつけてもつけなくても同じようにインスタンスを作成 - yuyakkoの日記
初期化の部分を別の関数に分けておけばコピペせずにコンストラクタを使い回せそうだったので試す。
最初は失敗。
function SafeConstructor(){ var callee = arguments.callee; if(this instanceof callee){ this.init.apply(this, arguments); }else{ var EmptyConstructor = function(){} EmptyConstructor.prototype = callee.prototype; var obj = new EmptyConstructor; obj.init.apply(obj, arguments); return obj; } } var Class1 = SafeConstructor; Class1.prototype.init = function(){ this.arg = Array.apply(this, arguments); }; Class1.prototype.toString = function(){ return "Class1/"+this.arg.toString(); } var Class2 = SafeConstructor; Class2.prototype.init = function(){ this.arg = Array.apply(this, arguments); }; Class2.prototype.toString = function(){ return "Class2/"+this.arg.toString(); } // 使ってみる var instance1 = new Class1(1, 2); var instance2 = Class2(3, 4, 5); alert("instance1="+instance1+"\n" +"Class1のインスタンス"+(instance1 instanceof Class1 ? "です" : "ではありません")+"\n" +"Class2のインスタンス"+(instance1 instanceof Class2 ? "です" : "ではありません")+"\n" +"\n" +"instance2="+instance2+"\n" +"Class1のインスタンス"+(instance2 instanceof Class1 ? "です" : "ではありません")+"\n" +"Class2のインスタンス"+(instance2 instanceof Class2 ? "です" : "ではありません"));
コンストラクタ作成関数を作って毎回違うオブジェクトにすればよさそう。
function makeSafeConstructor(){ return function(){ var callee = arguments.callee; if(this instanceof callee){ this.init.apply(this, arguments); }else{ var EmptyConstructor = function(){} EmptyConstructor.prototype = callee.prototype; var obj = new EmptyConstructor; obj.init.apply(obj, arguments); return obj; } }; } // 一部省略 var Class1 = makeSafeConstructor(); var Class2 = makeSafeConstructor();
"init"っていう名前を与えなくても無名関数でいいんじゃないかな。
初期化関数は引数で受け取るように変えてみる。
function makeSafeConstructor(init){ var SafeConstructor = function(){ if(this instanceof arguments.callee){ init.apply(this, arguments); }else{ var EmptyConstructor = function(){} EmptyConstructor.prototype = arguments.callee.prototype; var obj = new EmptyConstructor; init.apply(obj, arguments); return obj; } } return SafeConstructor; } // いろいろ省略 var Class1 = makeSafeConstructor(function(){ this.arg = Array.apply(this, arguments); });
初期化関数なんてケチなこと言わずにコンストラクタを受け取るようにしてもいいかも。
function makeSafeConstructor(Constructor){ var SafeConstructor = function(){ if(this instanceof arguments.callee){ return Constructor.apply(this, arguments); }else{ var EmptyConstructor = function(){} EmptyConstructor.prototype = arguments.callee.prototype; var obj = new EmptyConstructor; var result = Constructor.apply(obj, arguments); if(result instanceof Object){ return result; } return obj; } } SafeConstructor.prototype = Constructor.prototype; return SafeConstructor; } // makeSafeConstructor使わずにClass1作って… function Class1(){ this.arg = Array.apply(this, arguments); } Class1.prototype.toString = function(){ return "Class1/"+this.arg.toString(); } // 後から入れ替え Class1 = makeSafeConstructor(Class1); // 最初から使うように書いてもOK? var Class2 = makeSafeConstructor(function(){ this.arg = Array.apply(this, arguments); }); Class2.prototype.toString = function(){ return "Class2/"+this.arg.toString(); } // 使ってみるところは省略
JavaScriptでnew演算子をつけてもつけなくても同じようにインスタンスを作成
またJavaScriptで遊んでる。
JavaScriptにもnew演算子があった。クラスを定義してからnewでインスタンスを作成するということができるらしい。
コンストラクタになる関数を作ってからそれをnewの後に付けて呼べばいい。
function Class1(){ this.str = "これはClass1のインスタンス"; } Class1.prototype.toString = function(){ return this.str; } var instance1 = new Class1(); alert(instance1);
コンストラクタが呼ばれたとき、thisが新しいインスタンスになっているので好きなように初期化する。
でもこれ、new書き忘れたら大変なことになりますね。Class1を普通に関数として呼んだ時のthisは何でしたっけ。ブラウザの場合はwindow?
function Class1(){ this.str = "これはClass1のインスタンス"; } Class1.prototype.toString = function(){ return this.str; } var instance1 = new Class1(); var instance2 = Class1(); alert("instance1="+instance1+"\n" +"instance2="+instance2+"\n" +"window.str="+window.str);
instanceof演算子を使うとthisがClass1のインスタンスかどうか調べられるので例外を投げることができる。
function Class1(){ if(this instanceof Class1){ this.str = "これはClass1のインスタンス"; }else{ throw Error("Class1 is a constructor"); } } Class1.prototype.toString = function(){ return this.str; } var instance1 = new Class1(); var instance2 = Class1(); alert("instance1="+instance1+"\n" +"instance2="+instance2+"\n" +"window.str="+window.str);
せっかくだから新しいインスタンスを作って返すようにする。
function Class1(){ if(this instanceof Class1){ this.str = "これはClass1のインスタンス"; }else{ return new Class1(); } } Class1.prototype.toString = function(){ return this.str; } var instance1 = new Class1(); var instance2 = Class1(); alert("instance1="+instance1+"\n" +"instance2="+instance2+"\n" +"window.str="+window.str);
だいぶいいですね。組み込みオブジェクトみたいにnewがあってもなくても同じ物が作れそうです。
でもまだコンストラクタの引数の数が決まってないような場合には使えない。
function Class1(){ if(this instanceof Class1){ this.length = arguments.length; }else{ return new Class1(); } } Class1.prototype.toString = function(){ return this.length.toString(); } var instance1 = new Class1(1, 2); var instance2 = Class1(3, 4, 5); alert("instance1="+instance1+"\n" +"instance2="+instance2);
一方、ECMAScript Language Specificationによれば組み込みオブジェクトのArrayでは引数の数が決まっていません。
Array ( [ item1 [ , item2 [ , … ] ] ] )
new Array ( [ item0 [ , item1 [ , … ] ] ] )
しかもnewがあってもなくても作られるオブジェクトが全く同じになるそうです。
var array1 = new Array(1, 2); var array2 = Array(3, 4, 5); alert("array1.length="+array1.length+"\n" +"array2.length="+array2.length);
受け取った可変個の引数を別の関数に渡すのはFunction.prototype.applyでできる。
function Class1(){ if(this instanceof Class1){ this.length = arguments.length; }else{ var obj = new Class1(); Class1.apply(obj, arguments); return obj; } } Class1.prototype.toString = function(){ return this.length.toString(); } var instance1 = new Class1(1, 2); var instance2 = Class1(3, 4, 5); alert("instance1="+instance1+"\n" +"instance2="+instance2);
なんかnewしつつもClass1の呼び出しはapplyでできるようなこんな感じの…
var obj = new Class1.apply(???, arguments);
無理。これだとnewされるのはClass1じゃなくてClass1.applyの方だし、この時点ではまだオブジェクトが作成されていないのでthisArg*1は書きようがない。
new演算子の中身を調べる。
var instance1 = new Class1(1, 2);
ECMAScript Language Specificationにはいろいろ書いてあるんですが、結局のところnew演算子の仕事はClass1の内部メソッド*2[[Construct]]を呼ぶことらしい。
[[Construct]]が何をするかというと…
1. 新しいオブジェクトobjを作る。
2. Class1.prototypeの値をobjの内部プロパティ*3[[Prototype]]に入れる。
3. objをthisとして与えてClass1を呼ぶ。
面倒なのを省略すればこの3つになる。
1は var obj = new Object(); でできる。3は Class1.apply(obj, arguments); でできる。2は内部プロパティに手を出さないといけないので難しそうです。FirefoxとChromeでは__proto__という名前らしいんですが、言語仕様には出てきません。このままだと実装依存になってしまう。
探し回っていたら素晴らしい技が見つかりました。こういうやり方があるらしい。
var Traits = function () {}; Traits.prototype = SuperClass.prototype; SubClass.prototype = new Traits();
何もしないコンストラクタを作ってprototypeだけ設定しておく。そのコンストラクタを使ってnewすれば余計な初期化はしないで済むというわけ。
この技を使って…
function Class1(){ if(this instanceof Class1){ this.length = arguments.length; }else{ var Tmp = function(){} Tmp.prototype = Class1.prototype; var obj = new Tmp(); Class1.apply(obj, arguments); return obj; } } Class1.prototype.toString = function(){ return this.length.toString(); } var instance1 = new Class1(1, 2); var instance2 = Class1(3, 4, 5); alert("instance1="+instance1+"\n" +"instance2="+instance2);
本当にClass1のインスタンスになってるのかな。
function Class1(){ if(this instanceof Class1){ this.str = "new演算子で作成"; this.arg = Array.apply(this, arguments); }else{ var Tmp = function(){} Tmp.prototype = Class1.prototype; var obj = new Tmp(); Class1.apply(obj, arguments); obj.str = "Class1関数で作成" // 目印つける return obj; } } var instance1 = new Class1(1, 2); var instance2 = Class1(3, 4, 5); alert("instance1.str="+instance1.str+"\n" +"instance1.arg="+instance1.arg+"\n" +"Class1のインスタンス"+(instance1 instanceof Class1 ? "です" : "ではありません")+"\n" +"\n" +"instance2.str="+instance2.str+"\n" +"instance2.arg="+instance2.arg+"\n" +"Class1のインスタンス"+(instance2 instanceof Class1 ? "です" : "ではありません"));
コピペで使いまわせるようにarguments.calleeを使ったりして書く。
function Class1(){ if(this instanceof arguments.callee){ // ここに固有の初期化処理を書く this.arg = Array.apply(this, arguments); }else{ var Tmp = function(){} Tmp.prototype = arguments.callee.prototype; var obj = new Tmp(); arguments.callee.apply(obj, arguments); return obj; } } Class1.prototype.toString = function(){ return this.arg.toString(); } // 使ってみる var instance1 = new Class1(1, 2); var instance2 = Class1(3, 4, 5); alert("instance1="+instance1+"\n" +"Class1のインスタンス"+(instance1 instanceof Class1 ? "です" : "ではありません")+"\n" +"\n" +"instance2="+instance2+"\n" +"Class1のインスタンス"+(instance2 instanceof Class1 ? "です" : "ではありません"));
できたけど、面倒。newを書き忘れなければいいだけだから、コンストラクタでthisをチェックして例外投げるのが最善。
参考
特殊文字をエスケープするJavaScript
またjavascriptで遊んでる。
NicoCacheのキャッシュフォルダに日本語を含んだフォルダを指定したい場合、cacheFolderに書くパスをUnicodeエスケープしないといけない。
例えばこのパスを指定したいなら
C:\ニコニコ動画のキャッシュ
こうしないといけない。
C:\\\u30cb\u30b3\u30cb\u30b3\u52d5\u753b\u306e\u30ad\u30e3\u30c3\u30b7\u30e5
この変換をしてくれる道具をjavascriptで書いた。
http://dl.dropbox.com/u/3243573/20100418/escape.html
上の箱に書き込むと下の箱に結果が出るのでコピペして使うだけ。
java.util.Properties | java.util.Propertiesのloadメソッドで読み込むファイル用。NicoCacheのconfig.propertiesもこれ。 |
JavaScript | JavaScriptの文字列リテラル用。 |
C | C言語の文字列リテラル用。JavaScriptとほとんど同じだけどトリグラフ除けが入ってる。 |
ime.nuを飛ばすGreasemonkeyスクリプト
javascriptで遊んでる。
tumblrのダッシュボードでJを押すと次のページに行くGreasemonkeyスクリプト
おなじみのGreasemonkeyスクリプトを入れてたんですが
- AutoPagerize
- Minibuffer
- LDRize
- ReblogCommand
- tumblr Dashboard jk disable
調子に乗ってスクロールし続けるとどんどん重くなるのでAutoPagerizeは切ることにした。
Next Pageのリンクをクリックするのが面倒なのでキー操作で行けるようにスクリプト書いてみた。
http://dl.dropbox.com/u/3243573/20100304/gotonextpage.user.js
この順番に並べてる。
Dashboardで下までスクロールしてからJキーを押すと次のページに行く。
上でKキーを押すと前のページの下の方に行く。
ページ遷移をまたいでデータ渡すのにGM_setValueを使ってるけど、これは永続的なので用途が少し違うような気がする。
ウィンドウにデータつけて渡せないのかな。
Googleリーダーのナビゲーションの幅を変える
Googleリーダーのナビゲーションが狭すぎて何件未読か表示されないので広くする。
ユーザースタイルシートで指定すればいいらしい。
FirefoxでStylishプラグインを入れてからスタイルを作ると便利。
@-moz-document url-prefix(https://www.google.com/reader), url-prefix(http://www.google.com/reader), url-prefix(http://www.google.co.jp/reader), url-prefix(https://www.google.co.jp/reader) { #nav,#sub-tree{width:360px !important;} #chrome{margin-left:360px !important;} }
Google Reader の余計なメニューを消すユーザスタイルシート(2) - えむもじら
ユーザスタイルシートによるカスタマイズ(2) - Stylish - えむもじら
Stylish :: Add-ons for Firefox