JScript で文字列内の変数を展開する関数を作ってみた

2012-04-20 21:11:21 | Programming > JScript
はじめに
文字列の連結が込み入ってくるとソースコードの可読性が落ちる。
var url = 'http://' + hostname + '/image/' + id + '.cgi?w=' + x + '&h=' + y;
こういった記述法は連結後の文字列がイメージしにくいのが欠点である。JScript の用途からすれば、やはり見やすさも考慮しておきたい。例えば Perl では以下のように文字列に変数を埋め込み、それを展開させることができる。
http://${hostname}/image/${id}.cgi?w=${x}&h=${y}
直感的なわかりやすさではこちらのほうが上である。$q は JScript でこれを実現するための関数である。


Include コンポーネント
基本的な機能の紹介やダウンロード先は Include コンポーネントのページを参照のこと。

構文
Include コンポーネントをアクティブ化することで $q という変数展開用の関数が使えるようになる。
($=new ActiveXObject('JScript.Include'))(this);
var url = $q('http://${hostname}/image/${id}.cgi?w=${x}&h=${y}');

上記の場合、$q を呼び出したコンテキストにおいて hostname, id, x, y という変数の値が参照され、文字列中の変数部分が置き換えられる。未定義の変数はエラーとなるので注意。


パース
添え字にも対応できるよう変数のパースは緩めに作ってある(実際はコードとして認識できれば変数以外でも可)。
($=new ActiveXObject('JScript.Include'))(this);
var url = $q('http://${hostnames[0]}/image/${idlist[i]}.cgi?w=${rect[0]}&h=${rect[1]}');


スクリプト コントロール化が必要な理由
なぜスクリプト コントロール化が必要なのか。まずは Include コンポーネントに実装されている $q 関数をスクリプト上で定義した場合、どのような挙動を示すか確認してみる。
function $q(str) {
	return str.replace(/\${([^{}]+)}/g, function ($0, $1) {
		return eval($1);
	});
}

function scopeA() {
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
このコードを実行すると変数 a の値はいずれも "global" となる。$q 関数はグローバル コンテキスト上で定義されているため、そのスコープチェーン(変数オブジェクトのリスト)は常に「$q 関数の Activation オブジェクト -> グローバル オブジェクト」となる。scopeA 内で $q 関数が呼ばれた場合、$q 関数の eval で参照してほしいのは scopeA のローカル変数であるが、scopeA の Activation オブジェクトは $q 関数のスコープチェーンに含まれないため、$q 関数が scopeA のローカル変数を参照することはない。ではどうするか。
function $q(str) {
	return str.replace(/\${([^{}]+)}/g, function ($0, $1) {
		return eval($1);
	});
}

function scopeA() {
	eval($q.toString());
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
上記は scopeA 内で eval を使って $q を再定義する一行を追加し、スコープチェーンに scopeA が含まれるようにしたものである。こうすれば一応期待通りの動作は得られる。関数ごとに $q を再定義というのはコーディングとしてスマートではないが、JScript だけで実現しようとするとこうする他ない。そこでスクリプト コンポーネントを利用する。
($=new ActiveXObject('JScript.Include'))(this);

function scopeA() {
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
$q 関数をコンポーネント側で定義し、それをスクリプト側から呼び出すと $q 内の eval が辿るスコープチェーンはコンポーネント側の $q のスコープチェーンではなく、呼び出し元コンテキストである scopeA のスコープチェーンとなる。これはコンポーネント側のスコープチェーンがスクリプト側のそれとは独立していることに起因する。つまり $q 関数はこの仕様を逆手にとり、動的スコープを実現している。スコープの独立性という点からすれば、スクリプト コントロールを使っても同様のことができると思われるが、コーディングの煩雑さを考えれば、あらかじめスクリプト コンポーネントを使ってモジュール化しておくほうが得策だろう。


JSCript > Include コンポーネント

最新の画像もっと見る