O'Reillyの「JavaScript 第六版」を読み始め、JavaScriptでオモチャを作って遊んでる。今はブログの背景色を自由に変えられるシロモノを作ろうと思った。きっと途中で挫折するけど。とりあえず今はスタイルシート中の背景色定義を拾い出す部品を作った。出来上がりは、こんな感じ。
body |
#container |
#banner |
.content |
#right |
プログラムは、大きく分けて2つの部分から成る。
- スタイルシートを検索し、背景色を設定しているクラスを抽出し、連想配列 BG に クラス名またはクラスID:背景色 のペアで登録する。
- 連想配列 BG から、HTML の <table>..</table> を作り出す。
最初は、ベタな感じで作ってた。→全ソースは末尾 v01
// スタイルシートからクラス名(又はクラスID):背景色の連想配列BGを作る
var BG = [];
..いろいろ
// BGから<table>..</table>を作る
var m0 = "";
..いろいろ
document.writeln(tbody.replace( /_tcolm_/m, m0 ) ); //HTMLを書き出す
変数 BG や m0 は、この処理だけの一時的な変数だ。ベタに書くと、いずれの変数もグローバル変数になって、気持ち悪い。JavaScript は、変数や関数のスコープは関数ごとに分かれる。なら無名関数を作り、すぐに実行よう。一時変数は関数が終われば消えるんで、外には影響しない。→全ソースは末尾 v02
(function(x) {
..いろいろ
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = [];
for( var i0=0; i0<r0.length; i0++ ) { ..いろいろ }
// BGから<table>..</table>を作る
var m0 = "";
for( var k0 in BG ) { ..いろいろ }
document.writeln(tbody.replace( /_tcolm_/m, m0 ) );
}() );
連想配列 BG を作る部分と、HTML を作る部分は独立している。共有する必要蛾あるのは、BGだけだ。が、上の形だと、HTML を作る際の一時変数 m0 などが、BG を作る部分にも見えてしまう。同様に BG を作る部分の一時変数も、HTML を作る部分で見えてしまう。なんか面白くないんで、それぞれ無名関数として、名前空間を分けよう。→詳細は末尾 v03
(function(x) {
..いろいろ
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = ( function() { ..いろいろ }() );
// BGから<table>..</table>を作る
var hText = ( function( BG ) { ..いろいろ }( BG ) );
document.writeln(tbody.replace( /_tcolm_/m, hText ) );
}() );
一時変数 BG も、関数の引数にしちゃえば不用だよね。よし、そうしよう。→詳細は末尾 v04
(function(x) {
..いろいろ
// BGから<table>..</table>を作る
var hText = ( function( BG ) {
var m0 = "";
for( var k0 in BG ) { ..いろいろ }
return m0;
}( function() {
// スタイルシートからクラス名:背景色の連想配列BGを作る
..いろいろ
}() ) )
);
document.writeln(tbody.replace( /_tcolm_/m, hText ) );
}() );
なんか関数型っぽくなってきたな。いっそのこと、for() {} の制御変数も無くせないかなあ。そういえば map とか join とかがあったなあ。でも、連想配列からキーだけ取り出す keys は私の環境じゃ使えないっぽいし、map は副作用が目的だとうまくいかないみたいだ。じゃ、自分で作ろう。
とか考えてたら、関数型っぽいプログラムになってしまった。
(function(x) {
var tbody = '<table cellspacing="1" style="background-color: rgb(176, 176, 68);"><tbody><tr>\n_tcolm_</tr></tbody></table>\n';
var tcolm = '<td style="background-color: _rgb_; ">_sName_</td>\n';
function cKeys( a ) { r=[]; for(var i in a ) r.push(i); return r; } //連想配列aからキーの配列を返す
function cForAll( a, f ) { for( var i=0; i<a.length; i++ ) f(a[i]); } //配列aの全要素に対し f(x)
// BGから<table>..</table>を作る
var hText = tbody.replace( /_tcolm_/m,
( function( BG ) {
return cKeys(BG).map( function(x) {
return tcolm.replace( /_rgb_/m, BG[x] ).replace( /_sName_/m, x);}).join("");
}( function() {
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = [];
cForAll( document.styleSheets, function( x ) {
cForAll( x.cssRules, function( y ) {
if( y.style.backgroundColor ) {
BG[y.selectorText] = y.style.backgroundColor;
}
} )
} );
return BG;
}() ) )
);
document.writeln( hText );
}() );
なんか、自然と関数型プログラムを書くよう誘導されてるなあ。こんな感じ?
- 一つのプログラムは、幾つかの部品からできる。それぞれの部品は、なるべく他の部品に影響させたくない。部品の中だけで使う一時変数は、他の部品から見えないようにしたい。
- JavaScript だと、変数のスコープは関数で区切る。なら、それぞれの部品は関数にすればいい。
- 無名の小さな関数がアチコチにできて、関数型っぽいプログラムになる。
その結果、プログラマの考え方も関数型になってくる。これは、JavaScript の2つの仕様が原因だ。
- 変数や関数のスコープは関数で区切る
- 無名関数が使える。
きっと、JavaScript の言語を設計した人が、LISP を布教するために画策した陰謀に違いない。そのうち、「無名関数は function じゃなく lambda を使おう」とか言い出すんだ。そして世界は総てS式になるのだ。わはは。
…てな馬鹿話はおいといて、以下、参考資料として、各段階のソース。
v01:ベタな書き方
var tbody = '<table cellspacing="1" style="background-color: rgb(176, 176, 68);"><tbody><tr>\n_tcolm_</tr></tbody></table>\n';
var tcolm = '<td style="background-color: _rgb_; ">_sName_</td>\n';
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = [];
var r0 = document.styleSheets ;
for( var i0=0; i0<r0.length; i0++ ) {
for( var i1=0; i1<r0[i0].cssRules.length; i1++ ) {
var r1 = r0[i0].cssRules[i1];
if( r1.style.backgroundColor ) {
BG[r1.selectorText] = r1.style.backgroundColor;
}
}
}
// BGから<table>..</table>を作る
var m0 = "";
for( var k0 in BG ) {
m0 += tcolm.replace( /_rgb_/m, BG[k0] ).replace( /_sName_/m, k0 );
}
document.writeln(tbody.replace( /_tcolm_/m, m0 ) );
v02:無名関数にする
(function(x) {
var tbody = '<table cellspacing="1" style="background-color: rgb(176, 176, 68);"><tbody><tr>\n_tcolm_</tr></tbody></table>\n';
var tcolm = '<td style="background-color: _rgb_; ">_sName_</td>\n';
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = [];
var r0 = document.styleSheets ;
for( var i0=0; i0<r0.length; i0++ ) {
for( var i1=0; i1<r0[i0].cssRules.length; i1++ ) {
var r1 = r0[i0].cssRules[i1];
if( r1.style.backgroundColor ) {
BG[r1.selectorText] = r1.style.backgroundColor;
}
}
}
// BGから<table>..</table>を作る
var m0 = "";
for( var k0 in BG ) {
m0 += tcolm.replace( /_rgb_/m, BG[k0] ).replace( /_sName_/m, k0 );
}
document.writeln(tbody.replace( /_tcolm_/m, m0 ) );
}() );
v03:BG作成と表作成を無名関数にする
(function(x) {
var tbody = '<table cellspacing="1" style="background-color: rgb(176, 176, 68);"><tbody><tr>\n_tcolm_</tr></tbody></table>\n';
var tcolm = '<td style="background-color: _rgb_; ">_sName_</td>\n';
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = ( function() {
var BG = [];
var r0 = document.styleSheets ;
for( var i0=0; i0<r0.length; i0++ ) {
for( var i1=0; i1<r0[i0].cssRules.length; i1++ ) {
var r1 = r0[i0].cssRules[i1];
if( r1.style.backgroundColor ) {
BG[r1.selectorText] = r1.style.backgroundColor;
}
}
}
return BG;
}() );
// BGから<table>..</table>を作る
var hText = ( function( BG ) {
var m0 = "";
for( var k0 in BG ) {
m0 += tcolm.replace( /_rgb_/m, BG[k0] ).replace( /_sName_/m, k0 );
}
return m0;
}( BG ) );
document.writeln(tbody.replace( /_tcolm_/m, hText ) );
}() );
v04:一時変数 BG を無くす
(function(x) {
var tbody = '<table cellspacing="1" style="background-color: rgb(176, 176, 68);"><tbody><tr>\n_tcolm_</tr></tbody></table>\n';
var tcolm = '<td style="background-color: _rgb_; ">_sName_</td>\n';
// BGから<table>..</table>を作る
var hText = ( function( BG ) {
var m0 = "";
for( var k0 in BG ) {
m0 += tcolm.replace( /_rgb_/m, BG[k0] ).replace( /_sName_/m, k0 );
}
return m0;
}( function() {
// スタイルシートからクラス名:背景色の連想配列BGを作る
var BG = [];
var r0 = document.styleSheets ;
for( var i0=0; i0<r0.length; i0++ ) {
for( var i1=0; i1<r0[i0].cssRules.length; i1++ ) {
var r1 = r0[i0].cssRules[i1];
if( r1.style.backgroundColor ) {
BG[r1.selectorText] = r1.style.backgroundColor;
}
}
}
return BG;
}() ) );
document.writeln(tbody.replace( /_tcolm_/m, hText ) );
}() );