v0.0 20011118
・リンク/転載/サンプルコードの利用は自由に行ってください。元ネタのほとんどは受け売りだし。
・ただし内容の正確さについては責任を負いません。実装する前に必ずご自分で検証してください。
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;
}
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
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 (var name in o){}
とすると、name変数にはオブジェクトoのプロパティ名が順番に代入される。
for (var name in o){
alert(name + "/" + o[name]);
}
このコードはオブジェクトoのプロパティ名/プロパティを順番にアラートボックスで表示する。var e =new Enumerator(c)とすると、
for( ; !e.atEnd() ; e.moveNext()){
Response.Write(e.item());
}
このようなコードでコレクションcの内容を列挙できる。item()メソッドでEnumeratorの各要素を得る。var i = e.item(); Response.Write(i.name + "/" + i.value);Scripting.Dictionaryもこの手法で列挙できる。
| 環境 | 関数 | 形式 | 例 |
|---|---|---|---|
| IE/JScript | escape | unicodeエスケープ(UCS-2?) | %uHHHH |
| IE/JScript | EncodeURI/EncodeURIComponent | UTF-8 | %HH%HH%HH |
| NN | escape | ShiftJisエスケープ | %HH%HH |
| ASP | Server.URLEncode | ShiftJisエスケープ | %HH%HH |
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メソッドを持っている。引数には進数を指定する。
//コンストラクタ
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秒 |
| append | 37.532 -> 37.592 = 0.06秒 | 12.275 -> 12.315 = 0.04秒 | 54.542 -> 54.992 = 0.45秒 |
function byteLength(str){
return Server.URLEncode(str).replace(/%(\d|[A-F]|[a-f]){2}/g, "%").length;
}
(2005/01/16 指摘により修正。thanx>館林さん)| ↓左 | →右 | null | undefined | NaN | "" | 0 | 1 | true | false |
|---|---|---|---|---|---|---|---|---|---|
| null | ○ | ○ | × | × | × | × | × | × | |
| undefined | ○ | ○ | × | × | × | × | × | × | |
| NaN | × | × | × | × | × | × | × | × | |
| "" | × | × | × | ○ | ○ | × | × | ○ | |
| 0 | × | × | × | ○ | ○ | × | × | ○ | |
| 1 | × | × | × | × | × | ○ | ○ | × | |
| true | × | × | × | × | × | ○ | ○ | × | |
| false | × | × | × | ○ | ○ | × | × | ○ | |
new String(12345)とやりがちだが、実際には
String(12345)とすれば文字列型の値を得られる。Boolean/Numberについても同様で、newが付いた場合はその型のオブジェクトが返ってくる。typeofすると"object"になる。
(someValue == undefined)とするとエラーになる。undefinedと比較するときは、
(typeof(somevalue) == "undefined")とする
(someValue == NaN)ではなく、
isNaN(someValue)とする。
| JScript | VBScript | COM/OLEVariant型(vartype) |
|---|---|---|
| undefined | Empty | VT_EMPTY(0) |
| null | Null | VT_NULL(1) |
| -- | Nothing | VT_DISPATCH(9) (NULL参照(0x00000000)を持つIDispatchオブジェクト) |
<Script language=vbscript> Function getNothing Set getNothing = Nothing End Function </script>これをスクリプトの先頭に記述すればよい。ただし異なる言語の共存はパフォーマンスを落とすので、勧められない。
var rs = new ActiveXObject("ADODB.RecordSet")
var nothing = rs.ActiveConnection;
生成直後のRecordSetオブジェクトのActiveConnectionプロパティはNothingになっている。
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のブロックを共存させる<script language=vbscript runat=server> Function JSArray2SafeArray(arStr) JSArray2SafeArray = split(arStr) End Function </script>
with(obj){
prop1 = "aaa";
prop2 = 123;
}
とすると、objのプロパティを"obj."という前置きなしに参照できる。コーディングの節約に役立つ構文だが、その特性は理解しておく必要がある。
with(Response){
Write(...)
Write(...)
Write(...)
}
とVB/VBScriptのノリで書いてしまうと、名前解決のために性能ががた落ちする。
var cn = Server.CreateObject("ADODB.Connection")
WSHの場合
var cn = new ActiveXObject("ADODB.Connection")
cn.open([接続文字列])接続文字列のサンプルはこちら。
cn.execute([SQL文])SQLの実行 - 列を返すクエリ
rs = cn.execute([SQL文])レコードセットをカスタマイズしたいときは、以下の通り
var rs = Server.CreateObject("ADODB.RecordSet")
rs.[プロパティ] = [値]
rs.open([SQL文])
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を参照。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 02:DBとは関係ない文脈であれば、以下の手が使えるらしい。var sGuid = new ActiveXObject("Scriptlet.Typelib").guid.substr(0, 38);
mailto:hir@imasy.or.jp