fc2ブログ
JavascriptのString.replaceに無名関数を渡して複雑な置換
投稿日時 : 2008-03-03 02:33
 JavascriptでString.replaceに無名関数を渡して、後方参照や変数などを絡めて複雑な置換処理ができる。

■サンプル

 以下のサンプルは文字列「a=%a : b=%b : c=%c」の%付きアルファベットをキーとして連想配列namedから値を取得し、置換するものです。実行結果は「a=0 : b=1 : c=2」となります。
var named = {a:0,b:1,c:2}; //連想配列(Object)
var str = 'a=%a : b=%b : c=%c'; //置換対象文字列
var reg = /%([a-c])/g; //正規表現オブジェクト生成
var result = str.replace(reg,
    function(whole,p1){ //無名関数定義
        return named[p1]; //連想配列から値を取り出す
    }
);
alert(result); //「a=0 : b=1 : c=2」と表示
replaceの中にごちゃごちゃ書きたくない場合は以下のように無名関数を格納した変数を渡してもOK。ただしこのコードを関数内に書くのであればrepはローカル変数になるので問題ないが、関数外に書く際はrepがグローバル変数である必要がないなら避けるべき。
無名関数への参照を格納した変数を渡す例
var named = {a:0,b:1,c:2}; //連想配列
var str = 'a=%a : b=%b : c=%c'; //置換対象文字列
var reg = /%([a-c])/g; //正規表現オブジェクト生成
var rep = function(whole,p1){ //無名関数の参照を変数repに格納
    return named[p1]; //連想配列から値を取り出す
} 
var result = str.replace(reg, rep); //変数repに格納した無名関数への参照を渡す
alert(result); //「a=0 : b=1 : c=2」と表示
後で同じ置換処理をしたいなら、無名関数ではなく名前付き関数を定義しておいてその参照を渡しても構わない。1回しか使わないなら無名関数を使うべき。
名前付き関数への参照を渡す例
var named = {a:0,b:1,c:2}; //連想配列
var str = 'a=%a : b=%b : c=%c'; //置換対象文字列
var reg = /%([a-c])/g; //正規表現オブジェクト生成
function rep(whole,p1){ //名前付き関数をあらかじめ定義
    return named[p1]; //連想配列から値を取り出す
} 
var result = str.replace(reg, rep); //名前付き関数repの参照を渡す
alert(result); //「a=0 : b=1 : c=2」と表示

■受け取る引数の詳細

replaceに関数オブジェクトを渡すと、実行時に引数が渡される。その数は
グループ"()"の数 + 3
となっている。冒頭のサンプルを少し改造して以下のようなコードで引数の数と中身を確認してみる。サンプルコードでは1番目の引数をwhole、2番目をp1と決め打ちしていたのをargumentsオブジェクトを参照して全ての引数の中身を見れるようにする。
var named = {a:0,b:1,c:2}; //連想配列
var str = 'a=%a : b=%b : c=%c';//置換対象文字列
var reg = /%([a-c])/g; //正規表現オブジェクト生成
var result = str.replace(reg,
    function(){ //無名関数定義
        var args = '';
        for(var i=0; i<arguments.length; i++){ //引数の数だけループ
            args += i + ' : ' + arguments[i] + '\n';
        }
        alert(args);
        return named[arguments[1]]; //連想配列から値を取り出す
    }
);
alert(result); //「a=0 : b=1 : c=2」と表示
グループ数が1つの場合の受け取る引数の中身。グローバルオプション(g)付きなので3回マッチする。
1回目のマッチ
0 : %a
1 : a
2 : 2
3 : a=%a : b=%b : c=%c
2回目のマッチ
0 : %b
1 : b
2 : 9
3 : a=%a : b=%b : c=%c
3回目のマッチ
0 : %c
1 : c
2 : 16
3 : a=%a : b=%b : c=%c
次にグループ数を以下のように3つに増やして実行してみる。
var reg = /((%)([a-c]))/; //正規表現オブジェクト生成
グループ数が3つの場合の受け取る引数の中身
1回目のマッチ
0 : %a
1 : %a
2 : %
3 : a
4 : 2
5 : a=%a : b=%b : c=%c
2回目のマッチ
0 : %b
1 : %b
2 : %
3 : b
4 : 9
5 : a=%a : b=%b : c=%c
3回目のマッチ
0 : %c
1 : %c
2 : %
3 : c
4 : 16
5 : a=%a : b=%b : c=%c
次にグループ数を以下のように10個に増やして実行してみる。
var reg = /((%)([a-c]))()()()()()()()/; //正規表現オブジェクト生成
0 : %a
1 : %a
2 : %
3 : a
4 : 
5 : 
6 : 
7 : 
8 : 
9 : 
10 : 
11 : 2
12 : a=%a : b=%b : c=%c
以上の実験からまとめると、
  • arguments[0]はマッチした文字列全体
  • arguments[1] ~ arguments[arguments.length-3]はグループ単位でマッチした文字列。通常の後方参照で使う$1はaruguments[1]、$2はarguments[2]・・・となっている。参照できるグループの数に制限はない(詳しくは調べていないが100個以上のグループでも問題なく参照できる)。
  • arguments[arguments.length-2]はマッチした文字列が先頭から何文字目にあるか
  • arguments[arguments.length-1]は検索した文字列全体
ということになる。

■後方参照の展開のタイミング

 replaceの2番目の引数に無名関数を渡せると知らなかった頃、以下のようにマッチした文字列の後方参照$1を関数に渡して、処理した結果を置換文字列として使おうとしていたがうまく動かなかった。
これでは期待した動作はしない
var named = {a:0,b:1,c:2}; //連想配列
var str = 'a=%a : b=%b : c=%c'; //置換対象文字列
var reg = /%([a-e])/g; //正規表現オブジェクト生成
var rep = function(str){ //無名関数を変数repに格納
    return named[str]; //連想配列から値を取り出す
}
var result = str.replace(reg, rep("$1"));
alert(result); //「a=undefined : b=undefined : c=undefined」と表示
これは$1が実際のマッチ文字列に展開されるタイミングが期待より遅いのが原因。展開が行われるのはreplaceの2番目の引数「rep("$1")」が評価された後。つまりrepに格納された無名関数には文字列「$1」自体が渡される。alert(str)とすれば確認できる。その結果、「$1」をキーとして連想配列namedを検索することになるので未定義値undefinedが返されていた。
 冒頭のサンプルコードに似ているが、rep()だと関数への参照ではなくrep()の実行結果をreplaceに渡すことになるので意味がぜんぜん違う。
 
スポンサーサイト



2008-03-03 02:33 | Javascript | Comment(0) | Trackback(1)
Comment
コメントを書く
#

管理者にだけ表示
Trackback
Trackbak URL:http://itmst.blog71.fc2.com/tb.php/74-adcc9c6e
[Javascript]replaceの第二引数に、無名関数を割り当てる場合。
以上の実験からまとめると、 * arguments[0]はマッチした文字列全体 * arguments[1] ~ arguments[arguments.length-3]はグループ単位でマッチした文字列。通常の後方参照で使う$1は aruguments[1]、$2はarguments[2]・・・となっている。参照できるグループの数に制限はな
  • 2009/11/17(火) 14:41:53 |
  • takekenの勉強メモ