gate > core > JScript > tips編

JScript -- tips編

v0.0 20011118 Visitor Count since 2002/02/04

総論編 | リソース編

 ・いろいろと気づいたことをまとめてみました。開発時のネタにお役立てください。

 ・リンク/転載/サンプルコードの利用は自由に行ってください。元ネタのほとんどは受け売りだし。

 ・ただし内容の正確さについては責任を負いません。実装する前に必ずご自分で検証してください。

 


JScript編

date値の変換処理、チェック処理、その他

・setDateなど()の返値は、結果として得られるDateオブジェクトの値をミリ秒に変換した値になる。(Javaと違ってvoidではない)そのため、
var today = new Date();
var 3daysLater = new Date(today.setDate(today.getDate() + 3));
という書き方ができる。

・ベタ入力の日付値(YYYYMMDD)の形式をチェックする関数。形式がまちがっている場合はfalseを返す。
netnewsで拾ったものに多少手を入れている。

/**
//=======================================================
//Name: isLeapYear
//Function:	引数の値が閏年か否かを判定する
//Param:	数値
//Return:	boolean
*/
function isLeapYear(y) {

	return (y%4!=0) ? false : (y%100!=0) ? true : (y%400!=0) ? false : true;

}

/**
//=======================================================
//Name: checkDate
//Function:	ベタ書き日付値の形式チェック
//Param:	Dateオブジェクト
//Return:	boolean
*/
function checkDate(d){

	//8ケタの数字であること
	//上位4桁が2000よりも上であること
	//中位2桁が01-12であること
	//下位2桁が中位に応じた日付の範囲内にあること

	//newsで拾ったものを改造

	//日付形式が4-2-2桁の数字であることを保証する
	var re = /^(\d{4})(\d{2})(\d{2})$/;

	if (!re.test(d)){
		return false;
	} // date has invalid format

	var dateParts = re.exec(d)

	//execしたあとの[0]要素にはdそのものが入る
	var year = dateParts[1];
	var month = dateParts[2];
	var day = dateParts[3];

	switch (parseInt(month, 10)) {	//ここのparseIntで進数指定が必須
		case 1 :	case 3 :	case 5 :	case 7 :	case 8 :	case 10:	case 12:
			if ((day < 1) || (day > 31)) {
				return false;
			}  // (day < 1) added
			break;
		case 4 :	case 6 :	case 9 :	case 11:
			if ((day < 1) || (day > 30)) {
				return false;
			}
			break;
		case 2 :
			if ((day < 1) || (day > (isLeapYear(year) ? 29 : 28))) {
				return false;
			}
			break;
		default:
			return false;
	}

	if ((year < 1996) || (2050 < year)){
		return false;
	}

	return true;

}

上記を時間値(HHMM)に応用したもの

function checkTime(t){

	//4桁の数字であること
	//上位2桁が00-23であること
	//下位2桁が00-59であること

	//時刻形式が2-2桁の数字であることを保証する
	var re = /^(\d{2})(\d{2})$/;

	if(!re.test(t)) {
		//alert("\n時間は HHMM 形式で入力してください。");
		return false;
    }

	var timeParts = re.exec(t);
	var h = timeParts[1];
	var m = timeParts[2];

	if( (h < 0) || (h > 23) ){
		return false;
    }

    if( (m < 0) || (m > 59) ){
		return false;
    }

	return true;

}


・ADOでストアドプロシージャの引数にdatetimeを使うとき、JScriptのDateオブジェクトを渡してもエラーになる。VT_DATE(YYYY/MM/DD)形式に変換しないといけない。また、ユーザにブラウザから日付を入力してもらうときや、ページ間で日付値を渡すときはベタ書きの方がなにかと都合がよい。
とすると、ベタ書き<>VT_DATE<>Dateオブジェクト、と相互に形式変換できるとありがたい。

というわけで、変換関数ライブラリ。coming soon

・Dateオブジェクトに関するディープな研究文書。
http://www.merlyn.demon.co.uk/js-dates.htm


オブジェクト、for-in構文

オブジェクトにプロパティを与えるには。
var o =new Object();
o.propertyA = 111;
o.propertyB = "aaa";
o.propertyC = new Date();
とする。プロパティは動的に追加できる。

以下のように、ユーザ定義オブジェクトのコンストラクタに書くこともできる。
function propObject(){
	this.propertyA = 111;
	this.propertyB = "aaa";
	this.propertyC = new Date();
}
o.propertyAはo["propertyA"]と書くこともできる。つまり、
var propName = "propertyA";
o[propName] = 111;
これは、
o.propertyA=111;
と同じ結果になる。面白い性質である。

あるオブジェクトのプロパティを列挙するにはfor-in構文を使う。
for (var name in o){}
とすると、name変数にはオブジェクトoのプロパティ名が順番に代入される。
for (var name in o){
	alert(name + "/" + o[name]);
}
このコードはオブジェクトoのプロパティ名/プロパティを順番にアラートボックスで表示する。
プロパティ名を知らなくても内容を列挙できるため、非常に便利である。
逆にプロパティ名は固定なので、定数のカタログとしても使える。

ただし、いちいちプロパティ名をevalしているのと同じなので、遅い処理になる。積極的に使うべきではない。
配列もfor-in構文で列挙できるが、普通のfor文を使った方がよい。
また、メソッドもプロパティとして見えてしまう(メソッドのソースが値として返る)ので、必要ない場合はtypeofで振り分ける必要がある。

この構文では、取得できるプロパティの順番は不定である。

プロパティの削除はdeleteメソッドで行う。deleteされたメソッドの値はundefinedに戻り、for-in構文にかけても見えなくなる。

ただし、var付きで宣言したグローバル変数はdeleteしても消えない。(エラーも出さない)消したい変数はvar抜きで使う必要がある。


Scripting.Dictionaryはいらない?

JavaScriptのオブジェクトはperlの連想配列にほぼ等しい。
Windows ScriptではScripting.Dictionaryが連想配列の機能を提供するが、JavaScriptのオブジェクトで十分に代替できるので、あえて使う理由はほとんどない。
ただし、SafeArrayの生成には便利。


コレクション、Enumeratorオブジェクト

ASPのRequest.QueryString/Form、ADOのRecordSet.Fieldsなど、COMを扱っているとCollectionオブジェクトが多数登場する。これらもfor-in構文で列挙できると考えがちだが、実際にやってみるとできないことに気づく。(forループが実行されない)
残念なことに、VBのfor-each-in構文とJScriptのfor-in構文は等価ではない。

これらのCollectionオブジェクトをfor-in構文で列挙するには、Enumeratorオブジェクトでラップする。
var e =new Enumerator(c)
とすると、
for( ; !e.atEnd() ;  e.moveNext()){
	Response.Write(e.item());
}
このようなコードでコレクションcの内容を列挙できる。item()メソッドでEnumeratorの各要素を得る。
コレクションでないオブジェクトをEnumeratorの引数にすると「オブジェクトはコレクションではありません」というエラーになる。

現実的にはコレクションの各要素もオブジェクトなので、以下のような記述が必要だろう。
var i = e.item();
Response.Write(i.name + "/" + i.value);
Scripting.Dictionaryもこの手法で列挙できる。


escapeの非互換性

Netscape Communicatorでは、escapeメソッドは引数をSJISの文字列として扱う。
対してInternet Explorer4以降では、escapeの引数はunicodeで変換される。
文字列エスケープを行うメソッドを以下に比較してみた。

環境関数形式
IE/JScriptescapeunicodeエスケープ(UCS-2?)%uHHHH
IE/JScriptEncodeURI/EncodeURIComponentUTF-8%HH%HH%HH
NNescapeShiftJisエスケープ%HH%HH
ASPServer.URLEncodeShiftJisエスケープ%HH%HH

EncodeURIはUTF-8でエンコードされるのだが、これを受け入れてくれるwebサーバはIISくらいしかないので、ほとんど使い道はないと思われる。

IE/NNの非互換の経緯はこちらのページが詳しい。
http://www.felix.or.jp/~yugo/js/guide/escape.html


NetscapeのescapeをUnicode対応に -- 続:escapeの非互換性

さて、NNとIEの間でescapeメソッドの非互換が明らかになった。
location.hrefでページ遷移しつつ、2バイト文字をQueryStringで渡したいときは結構困る。
(もちろん、普通こんな作り方はしないが。普通でないものの世話をしなきゃいけないこともある)
Netscapeでunicodeエスケープをするためのコードはこんな感じ。

function escapeUnicode(str){
	var ret = "";
	var len = str.length;
	for(var i = 0 : i < len ; i++){
		var val = str.charCodeAt(i);
		if(val <= 0xFF){
			ret += "%" + val.toString(16);
		} else {
			ret += "%u" + val.toString(16);
		}
	}
	return ret;
}
数値(Number)型の変数はtoStringメソッドを持っている。引数には進数を指定する。
引数に16を与えると、16進数に変換される。ASCII文字までエスケープされてしまうので多少効率が悪いが、そこはご勘弁。

こちらのページに、詳細な解説とより効率の良いコードがある。
http://user.ecc.u-tokyo.ac.jp/~t10521/ecmascript/tips/escape.txt


ニセStringBuffer

JScriptで長い文字列の処理をすると結構処理が重くなる。ので、上で参照したページからヒントを得てStringBufferのまねをしてみようと思い立つ。

//コンストラクタ
function StringBuffer(){
	this.buffer = new Array();
}

//バッファに文字列を追記する
function StringBuffer_append(strAppend){

	this.buffer[this.buffer.length] = strAppend;
}

//バッファの内容を返す
function StringBuffer_toString(){

	return this.buffer.join("");

}

//バッファをクリアする
function StringBuffer_clear(){

	this.buffer = new Array();

}

//バッファ文字列の長さを得る
function StringBuffer_length(){

	var len = 0;
	for(var i = 0 ; i < this.buffer.length ; i++){
		len += this.buffer[i].length;
	}
	return len;
	//return this.buffer.join("").length
}

StringBuffer.prototype.append = StringBuffer_append;
StringBuffer.prototype.toString = StringBuffer_toString;
StringBuffer.prototype.clear = StringBuffer_clear;
StringBuffer.prototype.length = StringBuffer_length;
使い方はこんな感じ。
var sb = new StringBuffer();
sb.append("aaaaa");
sb.append("bbbbb");
Response.Write(sb.toString());
ベンチ結果は以下の通り

ループ回数・文字数→100回 15900字1000回 159000字10000回 1590000字
+=36.030 -> 37.532 = 1.5秒10.813 -> 12.275 = 1.46秒13.280 -> 54.542 = 41.3秒
append37.532 -> 37.592 = 0.06秒12.275 -> 12.315 = 0.04秒54.542 -> 54.992 = 0.45秒

確かに速くなるが、実際には文字列をいちいち一つの変数に結合せずに、たくさん変数を宣言して細切れに保存しているだけだったりする
こんなに大きい文字列は扱わずに済むのが一番かも。


文字列のバイト長を取得するには/文字の全角・半角を区別するには

unicode的にはすべての文字は2バイトなので、文字数*2とすればよいのだが、それではあまり解決にならない局面のほうが多いだろう。
SJISでのバイト長を得たいところだが、JScriptにはSJISで文字を扱うメソッドが存在しない。
VBScriptにはASC関数があるが、せっかくなのでJScriptのみでなんとかしてみる。

Server.URLEncodeは、与えられた文字列をSJISでエンコードする。形式は以下の通り。
1バイト文字:
 アルファベットはそのまま、その他は"%"+ASCIIコードの16進表現
2バイト文字:
 1バイト目は"%"+バイトの16進表現
 2バイト目はASCIIでアルファベットとして表現できればその文字、それ以外は"%"+バイトの16進表現

というわけで、エンコードされた文字列の"%nn"を"%"に置き換えて、その長さを数えればバイト数が得られる。これを実現するコードは以下の通り。ただしASP限定。
function byteLength(str){
	return Server.URLEncode(str).replace(/%(\d|[A-F]|[a-f]){2}/g, "%").length;
}
(2005/01/16 指摘により修正。thanx>館林さん)
VBでCOM作って呼び出したり、一文字ずつASCで調べるよりはよほど効率的。
VBScriptでも同じコードは書けるが、正規表現の扱いがエレガントでない。

VBScriptでバイト長を得る関数のサンプルはこちら。
http://www.users.gr.jp/ml/archive/asp/1334.asp
http://www.users.gr.jp/ml/archive/asp/7403.asp


無を表す値

無を表す値にはいろいろあるが、JScriptでは以下の五つが挙げられる。
・"" -- 長さ0の文字列
・0 -- 数値0
・null -- ヌル
・undefined -- 初期化されていない変数が持つ特殊な値
・番外:NaN -- 数値でない値を表す特殊な値 parseInt()に文字列を与えたりすると返ってくる

これらの値を==で比較すると暗黙の型変換が行われて真になることがある。
以下に比較結果を示す。

↓左→右nullundefinedNaN""01truefalse
null××××××
undefined××××××
NaN××××××××
""×××××
0×××××
1××××××
true××××××
false×××××

正確な型変換結果の一覧はこちら。
http://www.west.com.ua/doc/oReilly/jscript/ch09_01.htm#JSCRIPT-CH-FURTHER-TAB-CONVERSION
型も含めて厳密に比較するには、"==="演算子を使う。

その他、陥りやすい罠:
・typeof(null)はnullではなく、objectである。javaと同じ感覚で比較していると陥りやすい。

・ある値をStringに変換したいとき、Javaの癖で
new String(12345)
とやりがちだが、実際には
String(12345)
とすれば文字列型の値を得られる。Boolean/Numberについても同様で、newが付いた場合はその型のオブジェクトが返ってくる。typeofすると"object"になる。
端的に言うと、new String()という書き方には意味がない。

・ある変数が未定義かどうか確かめたいとき、
 (someValue == undefined)
とするとエラーになる。undefinedと比較するときは、
 (typeof(somevalue) == "undefined")
とする

NaNをチェックするときは
 (someValue == NaN)
ではなく、
 isNaN(someValue)
とする。

同様にVBScriptでは、無を表す特殊値は以下の3つがある。
・Empty -- 未初期化の変数が持つ特殊な値
・Null -- ヌル値
・Nothing -- 空のオブジェクト参照を示す特殊な値

Windows内部では、これらの値の実体は以下の通り。

JScriptVBScriptCOM/OLEVariant型(vartype)
undefinedEmptyVT_EMPTY(0)
nullNullVT_NULL(1)
-- NothingVT_DISPATCH(9)
(NULL参照(0x00000000)を持つIDispatchオブジェクト)

ところで、JScriptではNothingに相当する値が存在しない。
厳密にオブジェクト参照を解放するにはNothingを代入する必要があるが、JScriptではこれができないことになる。切断されたRecordSetが欲しいときなどに問題になる。
解決法は次の項で。


Nothingの作り方

[参照news記事]
JScriptではNothingを作ることができないが、COMなどを経由して生成する方法がいくつか存在する。
1:VBScriptのブロックを共存させる
<Script language=vbscript>
Function getNothing
	Set getNothing = Nothing
End Function
</script>
これをスクリプトの先頭に記述すればよい。ただし異なる言語の共存はパフォーマンスを落とすので、勧められない。

2:RecordSetを使う
var rs = new ActiveXObject("ADODB.RecordSet")
var nothing = rs.ActiveConnection;
生成直後のRecordSetオブジェクトのActiveConnectionプロパティはNothingになっている。
これを別の変数に待避しておけばよい。

3:JArgUtility
このCOMコンポーネントを使えば、Nothingにアクセスできる。このほかにもJScriptで役立つ機能が多数実装されている。Peter Torr氏による。
http://www.netspace.net.au/~torrboy/code/jargutil/


SafeArrayの作り方

JScriptの配列は実際にはCSV文字列だったりする。VBScriptのvartypeに食わせると8(VT_STRING)が返ってくることからもわかる。
詳しい解説がこちらのページにある。
http://www.yasdbooks.com/devaspcomp2/bonus/arrays.htm
VBScriptではSafeArrayが使われるが、JScript単体ではSafeArrayを作れない(VBArrayオブジェクトは読み込み専用)が、COMなどを経由して生成する方法がいくつか存在する。

1:Scripting.Dictionaryを使えばSafeArrayを生成できる。[元記事]
function JSArray2SafeArray(ar) {
	var dic = new ActiveXObject("Scripting.Dictionary");
	for (var i = 0; i < ar.length; i++){
		dic.add(i, ar[i]);
	}
	return dic.items();
}
2:VBScriptのブロックを共存させる
たとえば、CSV文字列をSafeArrayに変換する。
<script language=vbscript runat=server>
Function JSArray2SafeArray(arStr)
	JSArray2SafeArray = split(arStr)
End Function
</script>

3:JSafeArrayコンポーネントを使用する。
Peter Torr氏による。
http://www.netspace.net.au/~torrboy/code/


withの使い方

with(obj){
	prop1 = "aaa";
	prop2 = 123;
}
とすると、objのプロパティを"obj."という前置きなしに参照できる。コーディングの節約に役立つ構文だが、その特性は理解しておく必要がある。

1:プロパティ検索の順番
上記コードでは、以下の順番でプロパティが検索される。
1:objのインスタンス
2:objのプロトタイプ
3:グローバルオブジェクト
このため、ローカル変数や関係のないオブジェクトなどがスコープ内に混在していると、検索コストが膨大になる。

2:expandoの無効化
withのスコープ内では、objに動的にプロパティを追加することができない。

3:グローバル変数の隠蔽
スコープの外でcountという変数を宣言しているとする。
このとき、objに同じcountと言う名前のプロパティが存在すると、グローバル変数の方は参照できなくなる。
withの影響下に置かれるプロパティを陽に指定できないため、こういうことが起きる可能性がある。

1の特性のため、
with(Response){
	Write(...)
	Write(...)
	Write(...)
}
とVB/VBScriptのノリで書いてしまうと、名前解決のために性能ががた落ちする。
サーバサイド(特にCOM)ではあまり使うべきではないだろう。使うとしても使用範囲は極力小さくとどめるのが吉。
(VBScriptではちゃんと性能向上するので、ご安心を)


Visual InterDevについて

VisualStudioに付いてくるVisualInterDevはJScriptを扱える数少ないIDEの一つだが、その使用レポート。長くなったので別ページにて。


ADO編

オブジェクトの生成

当初はVBScriptのノリでset obj = Server.CreateObject(...)としていて、エラーで全く前に進めなかった。
JScriptの文法にはSetがない(オブジェクトを特別扱いしない)ので、以下のように書けばいい。

ASPの場合
var cn = Server.CreateObject("ADODB.Connection")
WSHの場合
var cn = new ActiveXObject("ADODB.Connection")

接続の仕方

cn.open([接続文字列])
接続文字列のサンプルはこちら。
http://www.able-consulting.com/ADO_Conn.htm

SQLの実行 - 列を返さないクエリ

cn.execute([SQL文])
SQLの実行 - 列を返すクエリ
rs = cn.execute([SQL文])
レコードセットをカスタマイズしたいときは、以下の通り
var rs = Server.CreateObject("ADODB.RecordSet")
rs.[プロパティ] = [値]
rs.open([SQL文])

ストアドプロシージャの実行

結果をパラメータで返さないものなら、
"exec [プロシージャ名] [引数1], [引数2]..."
というSQLを書いて実行できる。

パラメータの戻り値を取りたいなら、Commandオブジェクトを使う必要がある。
もっとも行数を節約した記述は以下の通り。
var cmd = Server.CreateObject("ADODB.Command")
cmd.commandText = [プロシージャ名]
cmd.commandType = adCmdStoredProc
cmd.parameters.append(cmd.createParameter([変数名], [型定数], [方向定数], [データ長], [引数])
cmd.execute() //結果がない場合
rs = cmd.execute() //結果がある場合
戻り値にアクセスするには、以下のように記述する
cmd.parameters.field(0).value	//数字の場合、n番目のパラメータの値
cmd.parameters.field("name").value	//文字列の場合、パラメータのnameプロパティに一致するものの値
定数の値はADOのドキュメントとadojavas.incを参照。

ADOのドキュメントでは、データ長は省略できることになっている。
しかしJScriptでかつ引数を渡したい場合は、省略するとエラーになる。intやdatetimeのデータ長はBooks Onlineでも探しにくく、非常に苦労した。
また、日付値(datetime)に対応するADOのデータ型がわからず、これまた試行錯誤。

調査の結果、日付値を渡すにはadDBTimeStampを使用する。
データ長はintが4、datetimeが8である。残りの型とデータ長の対応表はこちらを参照。
http://www.able-consulting.com/ADODataTypeEnum.htm

引数の型

日付値を渡そうとするとき、JavaScriptのdate値をそのままdatetime型パラメータに渡してもエラーになる。
datetime型パラメータには、VT_DATE形式の値を渡す必要がある。
実際には"yyyy/mm/dd mm:hh"という文字列形式になっていれば受け取ってくれるので、JavaScriptのdate値をこの形式に変換すればよい。


getRows, getString

MSDNによれば、RecordSetのデータ取得を高速化するにはgetRows/getStringを使うのが定石らしい。
JScriptでgetRows/getStringを使う方法は以下の通り。
var arrayRS = rs.getRows().toArray();
var strRS = rs.getString(adClipString, 10000, "", "", "NULL");//フォーマット(固定)、行数、列区切り文字、行区切り文字、null表記
getRowsの返値は2次元のSafeArrayなので、JScript配列として扱うにはtoArrayで変換する必要がある。あまり美しくないし、ループで回すときに一列の要素数を考慮する必要があってなかなか面倒。
VBArrayでラップすれば2次元のままで使えるが、これもあまり美しくない。

getStringはすべての列・行を文字列結合して、個別にアクセスする必要なしに返してしまうメソッド。例のようにtd/trタグで囲めば、いきなりHTML表ができあがる。カンマと改行にすればCSVになる。前後の余ったタグはご愛敬。
形が単純でもいいなら、getStringが最速なのは間違いない。


ストアドプロシージャの呼び出しコードを自動生成する

ストアドプロシージャの引数に関する情報を実行時に得るには、
Command.Parameters.Refresh()メソッドを実行する。
DBとの通信が発生するため、ユーザ向けアプリ(特にASP)では禁忌に等しいが、開発目的で使うには全く支障はない。
var con=new ActiveXObject("ADODB.Connection");
var cmd = new ActiveXObject("ADODB.Command");
con.open([データソース]);
cmd.ActiveConnection = con;
cmd.CommandText = [ストアドプロシージャの名前]
cmd.CommandType = adCmdStoredProc

cmd.parameters.refresh();
ここでparametersコレクションの中身を走査すると、実行に必要なパラメータの情報をすべて取得できる。
実コードは後日に。

ASP

変数のカプセル化

DBから得た1行分の値のセットなど、変数をひとかたまりで扱いたいとき、グローバル変数を片っ端から定義していると非常に見通しが悪くなる。
配列を使ってもいいが、数値だけで変数を区別するのはあまり人間的ではない。
JScriptでは連想配列のようにObjectを使えるので、以下のような使い方をしている。
function employee(){
	this.name = "";
	this.id = "";
	this.age = "";
	...
}
とすると、
var emp = new employee();
emp.name = rs.fields("name").value;
emp.id = rs.fields("id").value;
emp.age = rs.fields("age").value;
以上のように、構造体のまねができる。
コンストラクタで定義しなくてもプロパティは任意に追加できるのだが、オブジェクトとプロパティの関係を明確にできるのでこの形をとっている。


Request変数の扱い方

Requestオブジェクトの中身を変数に待避する場合、以下のように必要な変数名をプロパティとして定義しておく。
function cgi_params(){
	this.a="";
	this.b="";
	this.c="";
}

var param = new cgi_params();
for(var i in param){
	param[i] = Request.Form(i).item();
}
これならいらない変数(submitボタンとか)を格納せずにすむので、見通しがよい。


Request変数のエスケープ処理

cgi変数は必ずエスケープする必要があるが、読み込んでからいちいちエスケープするよりは、読み込みと同時に処理する方が簡潔になる。
前項を応用して、
for(var i in param){
	param[i] = Server.HTMLEncode(Request.Form(i).item());
}
とすると面倒がない。


COMのプロパティ呼び出し時は省略しない

前2項で、Request.Form(i).item()と書いた。
COMオブジェクトのプロパティにアクセスするとき、途中の修飾子を省略してプロパティ名だけでアクセスできるケースがある。ASP Request、ADO RecordSetなど。
これは暗黙にCOMの名前解決が行われるため、パフォーマンスに悪影響を及ぼす。

ここまではASPの定番なのだが、JScriptではより切実な理由がある。

たとえばRequest.QueryString("NAME")と略記すると、"NAME"という名前のCGI変数の値が返される。
ここでtypeof(Request.QueryString("NAME"))とすると、"object"が返ってくる。

stringではないのか? と思い、VBScriptでtypename関数に食わせてみる。
答えは"IStringList"。
詳細はMSDNで見てもらうとして、実際にばRequest.QueryString("NAME")の返値はIStringListオブジェクトのデフォルトプロパティである""の値なのだった。

さて、存在しないCGI変数"aaa"を参照しようとするとどうなるか。
VBScriptでRequest.QueryString("aaa")の返値を見ると、Emptyになる。

JScriptでRequest.QueryString("aaa")の返値を見ると、空文字列に見える。
ところが、""/null/undefinedのどれと比較しても結果が偽になる。
これではcheckboxやradioが未選択かどうかチェックできない。

Empty == undefinedなのに、いったいどうなっているのか?

この場合もtypeofの結果が"object"であることを考えると、JScriptではデフォルトプロパティの値ではなく、空のIStringListそのものが返ってくるように見える。
VBScriptでisEmptyをラップする関数を作ってみたり、new String()で囲ってみたりとさんざん悩んだあげく、
Request.QueryString("aaa").item()
とすると、undefinedが正常に返ってくることがわかった。

Requestオブジェクトを参照するときは、item()までフルに書くのが無難と思われる。


Application変数の使い方

レファレンスでは
Application("name") = "value"
となっているが、正確には
Application.Contents("name") = "value";
である。Sessionも同様。
ASP内でwscからApplicationやSession変数にアクセスするときは、Contentsまで記述しないとエラーになる。デフォルトプロパティを正しく参照できないためらしい。


Applicationの使いどころ/DBアクセスとの比較

Application変数にデータを入れることで、webアプリケーション内でデータを共有できる。ただしApplicationオブジェクトへのアクセスは直列化されるので、負荷の高いアプリケーションで頻繁に使用すると性能に影響があるかも知れない。
どの程度の大きさなら実用的な性能が得られるかは検証していないが、約20000バイトの文字列データでApplicationから取得するとき・DBから毎回取得するとき、の両者でベンチマークを取った。

(結果 後日追加)

SQLServerに関する限り、毎回DBから取得する方が速い、という結果になった。DBは並列アクセスが可能なので、このような結果になったと思われる。


JScriptオブジェクトは格納できない


たとえば、
function obj(){
	this.a = "";
	this.b = "";
	this.c = "";
}
var jobj = new obj();

Application("jscript") = jobj;
とすると「アパートメントスレッドのオブジェクトを格納することはできません」という趣旨のエラーが発生する。

このjobjをVBScriptのtypenameに与えると、"JScriptTypeInfo"という文字列が返る。
レジストリを検索してもこの文字列は出てこないので詳細は不明だが、JScriptのオブジェクトは内部的にアパートメントスレッドのCOMであるらしいと推察できる。
そんなバカな、という気もするが。

同じようにJScriptの配列もApplicationに格納できないので、少々困る。VBScriptの配列は問題ないのだが。
CSV文字列に変換して保存するしかないようだ。


GUIDの取り方

知る限り、2種類ほど手段がある。
1:SQLServerに接続できる場合、NewID関数を使用する。
こんな感じのプロシージャを作って、逐一呼び出して使っている。ただしASP側ではパラメータの型をvarchar(36)としないとうまくいかなかった。(adGUIDはエラーになった)
create procedure getGUID
	uniqueidentifier @id output
as
	select @id = NewID()
	return 0
2:DBとは関係ない文脈であれば、以下の手が使えるらしい。
var sGuid = new ActiveXObject("Scriptlet.Typelib").guid.substr(0, 38);


総論編 | リソース編


mailto:hir@imasy.or.jp