JavaScriptで本格プログラミング(3)

前回から結構あいだが開きましたが、今回はvar宣言について述べてみます。
var宣言と関数の関わりについて以下に例を出してみます。

やりたいこと

例えば以下のようなオブジェクトtargetがあるとき、


var target = {

  onclick : function() {

    alert("Click target!");

  },

  onfocus : function() {

    alert("Focus target!");

  }

};

このオブジェクトが直接持つon〜というメソッドを次のように上書きします。

target.on〜 = function() {

  alert("on〜");

};

例えば"onclick"を決め打ちであれば

target.onclick = function() {

  alert("onclick");

};

でよいですが、on〜が複数あり、何があるかわからない状況で、この変換を行う関数を作成することが目的です。

試行1

最初に考えるのは、for〜inループでon〜関数を付け替える方法だと思います。


function test(obj) {

  for(var i in obj) {

    if(i.indexOf("on", 0) == 0) {

      obj[i] = function() {

        alert(i);

      };

    }

  }

}

このコードを上記のtargetに対して実行すると以下のようにうまくいきません。

test(target);

target.onclick();  // onfocus

target.onfocus();  // onfocus

この理由は、test関数内で宣言した変数ionclickを付け替えたときとonfocusを付け替えたときで共有されていることにあります。
つまり、ループ1周目では変数iは確かにonclickでしたが、2周目にはonfocusになってしまいます。
そして1周目でも2周目でも変数の入れ物は同じですから、target.onclick();を実行してもtarget.onfocus();を実行しても、同じ変数iの内容を表示します。


試行2

では、ループの中で宣言している変数に代入しなおしてみてはどうでしょうか?


function test2(obj) {

  for(var i in obj) {

    if(i.indexOf("on", 0) == 0) {

      var name = i;

      obj[name] = function() {

        alert(name);

      };

    }

  }

}

これも残念ながら結果は一緒です。

test2(target);

target.onclick();  // onfocus

target.onfocus();  // onfocus

変数のスコープは宣言された関数内*1となりますが、関数が実行される度に変数が用意されます。つまり、関数の1回実行につき1つの入れ物となります。
test2関数の中でvar宣言のコードは何度も通過しますが、用意された変数nameは1つだけで、ループのたびに同じ入れ物に変数iの中身を代入します。
target.onclick();target.onfocus();も、同じ変数nameを参照しています。

試行3

これより、ループの1周ごとに異なる変数を用意するのも1つの解決案です。
これは、ループの内側に匿名関数を書くことで実現できます。


function test3(obj) {

  for(var i in obj) {

    if(i.indexOf("on", 0) == 0) {

      (function() {

        var name = i;

        obj[name] = function() {

          alert(name);

        };

      })();

    }

  }

}

これで期待した結果を得ることができました。

test3(target);

target.onclick();  // onclick

target.onfocus();  // onfocus

この匿名関数はfor〜inループの中でonclickonfocusでの2度実行されます。さらにその中の変数nameは、匿名関数が実行される度ごとに異なる入れ物となります。
つまり、ループがonclickのときの変数nameと、onfocusのときの変数nameは互いに独立しています。
そして、匿名関数の中でtarget.onclicktarget.onfocusを上書きしていますが、いざtarget.onclickが実行されるときはそれが実装されたときのnameを、target.onfocusも同様に実行時は実装されたときのnameをそれぞれ参照します。


※匿名関数でのnameの定義やiの受け渡しは引数を使うこともできます。


function test3(obj) {

  for(var i in obj) {

    if(i.indexOf("on", 0) == 0) {

      (function(obj, name) {

        obj[name] = function() {

          alert(name);

        };

      })(obj, i);

    }

  }

}

ポイント

注目したいのは、

  1. var宣言された変数は、そのすぐ外側の関数が実行される度ごとに生成されていること
  2. var宣言された変数は、そのすぐ外側の関数が実行し終わっても、(スコープ内)からであれば参照できること

の2点です。
これを利用することで、privateなフィールドとそれに対するpublicなアクセスメソッドを実装することができます。

*1:他の多くの言語のように、変数宣言のすぐ外側の中括弧ブロック内ではありません。JavaScriptではすぐ外側の関数ブロックとなります。

JavaScriptで本格プログラミング(2)

JavaScriptでよく言う「ユーザー定義オブジェクト」を作るための関数をクラスのコンストラクタだとするならば、staticフィールドはどのように作成するかというと…

とりあえず、コンストラクタのメンバに値を持たせる

次のように、prefixとsuffix、そしてprototypeにtoStringをオーバーライドしました。
(ここではprototypeにtoStringをオーバーライドするとどうなるかは省略します。)


function Bean() {



  var data;



  this.getValue = function() {

    return data;

  };



  this.setValue = function(value) {

    data = value;

  };



}





Bean.prefix = "value=";

Bean.suffix = ";";



Bean.prototype.toString = function() {

  return Bean.prefix + this.getValue() + Bean.suffix;

};

これを実行してみると以下のようになります。


var obj1 = new Bean();

obj1.setValue(1);



alert(obj1); // value=1;



var obj2 = new Bean();

obj2.setValue(2);



alert(obj1); // value=1;

alert(obj2); // value=2;



Bean.prefix = "data=";



alert(obj1); // data=1;

alert(obj2); // data=2;


さて、これでstaticなフィールド(もちろん関数の参照を入れておけば、staticメソッド)ができました。
ただし、これらはpublicフィールドですので、外から値を直接操作できないprivateフィールドも欲しくなりました。
なので、次はprivateなstaticフィールドをどのように作るか…を解説する前提となる、var宣言について述べたいと思います。

JavaScriptで本格プログラミング(1)

JavaScriptで多段継承したクラスをいくつも作ったり、public/privateなメンバ・メソッドを実装するにはどうしたら・・・。
と、それだけが「本格」ではありませんが、よく見るような“スクリプト”的なコードだけではなく、通常のコンパイラ言語のようなコードもECMAScriptでは記述可能です。

もともと、ECMAScriptの言語仕様には、プロトタイプを使った継承が(たしか…)謳われています。

@ITに「Ajax時代のJavaScriptプログラミング再入門」という記事が登場しました。
そこらへんを(できることなら各種ツール・フレームワーク等に依存せずに)解説してくれたら、JavaScriptの株ももう少し上がるのではと期待しています。

ECMAScriptのユーザー定義オブジェクト(≒クラス)

JAVAビーンを例に、privateメンバ"data"とpublicメソッド"getValue","setValue"を持つ"Bean"クラスを定義してみました。


function Bean() {



  var data;



  this.getValue = function() {

    return data;

  };



  this.setValue = function(value) {

    data = value;

  };



}

このクラスは、以下のように動作します。

var obj1 = new Bean();

obj1.setValue(1);



var obj2 = new Bean();

obj2.setValue(2);



alert(obj1.getValue()); // 1

alert(obj2.getValue()); // 2



alert(obj1.data); // undefined

alert(obj2.data); // undefined

では、static(各インスタンスにではなく、各クラスに対して1つ存在する)メンバ・メソッドはどうするのか…。
それはまたの機会に載せたいと思います。

演劇『ひばり』から目が離せなかった

※純粋な日記です。

ついさっきまでやっていた、NHK教育「劇場への招待」で放送していた演劇『ひばり』*1から目が離せなかった。すごかった・・・。

やっぱり、自分は演劇好きなのかなぁと思う。以前、たまたま余ったチケットで誘われて見た『パパの明日はわからない』*2で初めて演劇を見た。次に衝撃を受けたのが、映画『オペラ座の怪人』で、実際にミュージカルが見たくなった。そんな中での、今日の放送。

本当は、そろそろ寝ようか、チャンネル変えようかと思ったけど(実際には幕の間に一瞬変えたけど、気になってすぐ戻した)、目が釘付けっていう状態。見てよかった、大満足。

*1:ジャンヌ・ダルクを題材にした演劇作品

*2:劇団ふるさときゃらばんのサラリーマンミュージカル

他人のウェブページに任意のリンクを追加するブックマークレット

気になるページで使っているスクリプトをダウンロードしたいとき、アドレスバーにURLを直打ちしたくないので、作ってみました。


javascript:void)((function(){var n=document.createElement("div");var s=prompt("URL",location.toString())(;if(s){n.innerHTML='<a href="'+s+'" target="_blank">'+s+'</a>';document.body.appendChild(n);}})());

JScriptコンソールを進化させてWSHコンソールに。

JScriptだけでなく、VBScriptも使えるWSHのコンソールとなりました。

  1. 特徴
    • JScriptだけでなく、VBScriptも使用可能
      JScriptVBScriptをネストして起動*1できます。JScript()VBScript()というメソッドがあります。
    • コマンドも簡易的に実行可能(JScript)
      JScript//:cmd DOSコマンドとすると、そのDOSコマンドの実行結果が表示されます。
    • historyの保持,echoとpromptの変更対応
      エラーが発生しなかったコマンドの履歴を保持します。
      また、コマンド入力時に表示されるプロンプトの制御ができます。
    • 複数行コマンドにも対応
      • JScriptの場合
        コマンド蓄積中でないとき、;で終わる文字列の入力はすぐに実行されます。;で終わらない文字列の場合、そのコマンドから蓄積を開始します。
        コマンド蓄積中のとき、;のみの入力で蓄積されたコマンドを実行し、\のみの場合は蓄積を破棄します。それ以外の空白だけでない文字列は蓄積されます。
      • VBScriptの場合
        コマンド蓄積中でないとき、_で終わるか、For,Do,While,If,Select,With,Sub,Function,Classのいずれかで始まる文字列の場合、そのコマンドから蓄積を開始し、そうでない場合はすぐに実行されます。
        コマンド蓄積中の場合、_のみの入力で蓄積されたコマンドを実行し、\のみの場合は蓄積を破棄します。それ以外の空白だけでない文字列は蓄積されます。
    • リダイレクトに代わる機能(JScript)
      JScript()で起動したコンソールは標準入力,標準出力,標準エラー出力を利用しますが、JScript(入力ストリーム, 出力ストリーム, エラー出力ストリーム)とすることもできます。*2
  2. 構成
    直接起動しないでください。よく内容を確かめてからご自分の責任で実行してください。
    • WshConsole.wsf:下記のconsole.vbsとconsole.jsをこの順でロードしています。通常はこれを起動します。
    • console.vbsVBScriptのコンソール機能を提供*3しています。単独起動不可*4
    • console.jsJScriptのコンソール機能を提供しています。単独起動も可能です。
  3. オススメ
    • wsfファイルにユーティリティー関数
      既にwrite,writeln,printと私が作ったものを残してあります。
    • バッチファイルで起動
      起動部分はconsole.jsにあり、wscript.exeで起動されていた場合にcscript.exeで起動しなおします。
      なので最初からcscript.exeで起動するバッチファイルを用意すると僅かに起動が速くなります。
      バッチファイルにはCScript //Nologo 起動するファイル*5などと記述します。

【2007/02/23追記】
「リダイレクトに代わる」とか言ってしまった機能はほとんど代わりができていない気がします。ここは作ったばかりなので改良の余地が大いにあると思います。
たとえば、「とあるウェブページにある全てのリンクについて、そのタイトルとリンク先とターゲットの一覧をCSVファイルにまとめる」というお題をやってみました。(追記をする前のこのページでやってみました。)
コンソールのキャプチャ画像

*1:JScriptからJScriptや、VBscriptからVBScriptも可能

*2:入力ストリームから読み込んだ文字列を実行したら標準入力からの入力を要求する場合、処理が停止し復帰不可能(→Ctrl+C)となります。このような処理は記述できません。

*3:私がVBScriptは得意じゃない&好きじゃないので、JScriptに比べて多少貧弱な機能です。

*4:console.jsの冒頭の10行程度を移殖すれば可能と思われます。

*5:現状ではWshConsole.wsfまたはconsole.js

長いトンネル生活から抜けれそうですが…

仕事漬けで朝から終電まで(場合によりタクシー)の生活とはだんだんおさらばしてきました。とはいえ、シフト勤務のおかげでまだ帰りは終電近くで、ブログのネタを考えるどころか、夜PCを起動する時間もまだほとんどない状況です。
これから(急にはむりなので)だんだん復活したいなと思います。特に、自分の勉強のために。