多次元配列活用してデータと処理を分ける例
「ある値がどの範囲にあるか調べる」ネタから続けての配列データ活用
前回の「ある値がどの範囲にあるか調べる」ネタの続きとして、多次元配列を活用してデータと処理を分ける例を紹介してみます。
データと処理を分ける目的について
if~elseでウインドウサイズが●○の時は★☆の処理、という形での作り方の場合、判定するウインドウサイズを変更・追加・削除等をする場合に、if~elseで逐次判定処理を書いていた場合、変更したい値(判定するウインドウサイズ値や各スタイルの変更値)がどの場所にあるかを調べその部分を修正する、などを行いますが、それらをすべて配列データにすると、配列の値を変更するだけでよいので楽になる、というネタのために、ウインドウサイズ用配列から、値配列のインデックス番号を取得するための関数アイデアを紹介しました。
今回は具体的なスタイル判定処理を例として記述して、その変更処理と値データを分けることで、値データ配列を変更するだけの形になるようなサンプルを紹介します。
前回のプログラムソース
ウインドウサイズごとにページの要素の各スタイルなどを変更するためのプログラム記述を配列で範囲や変更する値などを用意し、その配列インデックスを求めるための関数(hm_wxv2)を配列処理的な方法で作るネタを紹介しました。
※関数名は適当です。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=[1024,800,500],
y=hm_wxv2(w,x),
a=[960,760,460,300][y]+'px',
b=[350,250,200,150][y]+'px',
c=[16,14,13,12][y]+'px',
d=[10,10,5,5][y]+'px',
e=[50,50,50,100][y]+'px',
f=[1,1,0,0][y]?'block':'none',
g=[0,0,1,1][y]?'block':'none';
【メイン横幅を a にする処理】
【メニュー横幅を b にする処理】
【メニューの文字を c にする処理】
【メニューのleftを d にする処理】
【メニューのtopを e にする処理】
【メニューのdisplayを f にする処理】
【フッターにあるメニューホバー用アイコンのdisplayを g にする処理】
}
//前回のメインネタ
function hm_wxv2(w,x){
var c=x.indexOf(w),d=[];
if(c==-1){
d=x.concat(w);
d.sort(function(a,b){return b-a;});
c=d.indexOf(w);
}
return c;
}
プログラムでは、関数 hm_c_change(w)に、取得したウインドウサイズ(横幅)を引数として渡すと、wの値がxで用意している範囲配列のどの位置にあるかを返す関数[hm_wxv2]からの戻り値 y が、変更する各値を用意した配列のインデックス番号になっています。
範囲の値を増やす場合は、配列x に降順で値を増やし、増やした数だけ変更する各値の配列の対応するインデックス番号の部分に値を増やすというものになります。
980を追加する場合の例
x=[1024,980,800,500]
a=[960,860,760,460,300]…
b=[350,300,250,200,150]…
c=[16,15,14,13,12]…
d=[10,10,10,5,5]…
e=[50,50,50,50,100]…
f=[1,1,1,0,0]…
g=[0,0,0,1,1]…
…のように各データ配列に値を追加する形になります。
また、例えばページにある商品画像横幅サイズを変更する処理を追加する場合、
h=[400,400,200,200][y] と【商品画像の横幅サイズを h にする】(前回この処理は文字で書いています)
を追加するというプログラムになっています。
ですが、前回のままでは、[hm_c_change(w)] 関数は、ある特定ページ専用となってしまい、配列xや変数a~gが直接関数内に記述してあるので、別のページで違う値に変更したい場合、値だけが違う同じ名前の関数を記述することになってしまいます。
ということで、今回はそれらの変更処理とページ毎に異なる可能性のあるデータを分けるという形に変更してみます。
変更処理部分を書いてみる
前回は文字列で書いてる処理部分を書いてみます
※とりあえずメイン要素のidを[b_id]、メニュー要素のidを[m_id]、アイコンのidを[i_id]としています。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=[1024,800,500],
y=hm_wxv2(w,x),e,
a=[960,760,460,300][y]+'px',
b=[350,250,200,150][y]+'px',
c=[16,14,13,12][y]+'px',
d=[10,10,5,5][y]+'px',
e=[50,50,50,100][y]+'px',
f=[1,1,0,0][y]?'block':'none',
g=[0,0,1,1][y]?'block':'none';
//変数eにメイン要素のエレメントを入れる
e=document.getElementById('b_id');
e.style.width = a; //【メイン横幅の処理】
//変数eにメニュ要素のエレメントを入れ直す
e=document.getElementById('m_id');
e.style.width = b; //【メニュー横幅の処理】
e.style.fontSize = c; //【メニューの文字の処理】
e.style.left = d; //【メニューのleftの処理】
e.style.top = e; //【メニューのtopの処理】
e.style.display = f; //【メニューのdisplayの処理】
//変数eにアイコン要素のエレメントを入れ直す
e=document.getElementById('i_id');
e.style.display = g; //【メニューホバー用アイコンのdisplayの処理】
}
function hm_wxv2(w,x){
var c=x.indexOf(w),d=[];
if(c==-1){
d=x.concat(w);
d.sort(function(a,b){return b-a;});
c=d.indexOf(w);
}
return c;
}
このサンプルを汎用処理とデータ関数に分ける形に変更してゆきます。
※今回、範囲値インデックスを取得するための[hm_wxv2]は変更点がないので、そのまま利用し、以下ソース記述は省略します。
データ部分を関数にしてみる
データのみを設定する関数を作ってデータと処理を分けてみました。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=d_c(0,-1),
y=hm_wxv2(w,x),
a=d_c(y,0)+'px',
b=d_c(y,1)+'px',
c=d_c(y,2)+'px',
d=d_c(y,3)+'px',
e=d_c(y,4)+'px',
f=d_c(y,5)?'block':'none',
g=d_c(y,6)?'block':'none';
e=document.getElementById('b_id');
e.style.width = a;
e=document.getElementById('m_id');
e.style.width = b;
e.style.fontSize = c;
e.style.left = d;
e.style.top = e;
e.style.display = f;
e=document.getElementById('i_id');
e.style.display = g;
}
function d_c(y,t){
var x=[1024,800,500],
z=[
[960,760,460,300],
[350,250,200,150],
[16,14,13,12],
[10,10,5,5],
[50,50,50,100],
[1,1,0,0],
[0,0,1,1]
];
return (t<0)?x:z[t][y];
}
function hm_wxv2(w,x){※同じなので省略}
データ設定用の関数[d_c(y,t)]内の配列zでさっそく二次元配列を使っています。
関数内でswitch文などを使って記述する方法もありますが、二次元配列にすることで引数値をインデックス番号にして値を返す処理を単純にできるかと思います。(多次元配列の活用…というよりは基本的な使い方かと思います)
データ設定用の関数[d_c(y,t)]は、引数tに-1以下の値を渡すと、ウインドウサイズ判定用の範囲配列データを配列で返します。この時はyの値は無視しますので何を渡してもよいので例では 0 を指定しています。
範囲配列データ以外の変更値データは二次元配列zで設定しています。引数y(ウインドウサイズからのインデックス番号)とt(前回プログラムのa~gに対応する番号)がそのまま配列インデックス番号に対応する形になっています。それらの値を渡すと変更値(数値)を直接返す形になっています。
変更処理関数を汎用関数化
各変更処理も、
e.style.width = b;
e.style.fontSize = c;
e.style.left = d;
e.style.top = e;
e.style.display = f;
といった感じで同じような記述が並んでいます。
データ関数からの戻り値を変数a~gに入れているので、変更する数が増えると変数の数が増えるというのもいまいちな印象ですので、ここはスタイルを変更する関数を用意することにします。
※例では、変更値はすべてpx(fontSizeも)に固定し、displayはblockなら v=1 、noneなら v=0を渡す形で作っています。
※関数名は[hm_sc(e,v,t)]としています。引数 e … エレメント、v … 変更値(関数d_cからの戻り値)、t … 変更したいスタイルのタイプ(width、fontSize、left等)
//t=[0:width] [1:fontSize] [2:left] [3:top] [4:display]
function hm_sc(e,v,t){
var d='px';
if(e){//エレメントがundefinedなら処理しない
switch(t){
case 0:e.style.width = v+d;break;
case 1:e.style.fontSize = v+d;break;
case 2:e.style.left = v+d;break;
case 3:e.style.top = v+d;break;
case 4:e.style.display = v?'block':'none';break;
}
}
}
また、例示のプログラムの場合、IDから取得したエレメントを渡すと、そのスタイルを変更する、という関数を用意しておくとあとでループ処理がしやすくなりますので、その形で関数を作ります。この時、他でもよく使うかと思いますので汎用関数として、idを渡すと、document.getElementById してエレメントを返す関数も作っておくことにします。
※関数名は[r_e(i)]としています。引数 i … ID名
//汎用関数【idを渡すと、エレメントを返す】
//もしHTML内に指定idがなければ undefined が返る
function r_e(i){return document.getElementById(i);}
プログラムはこんな感じになりました。↓
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=d_c(0,-1),y=hm_wxv2(w,x),e;
e=r_e('b_id'); //メイン要素のエレメント
hm_sc(e,d_c(y,0),0); //【メイン横幅の処理】
e=r_e('m_id'); //メニュ要素のエレメント
hm_sc(e,d_c(y,1),0); //【メニュー横幅の処理】
hm_sc(e,d_c(y,2),1); //【メニューの文字の処理】
hm_sc(e,d_c(y,3),2); //【メニューのleftの処理】
hm_sc(e,d_c(y,4),3); //【メニューのtopの処理】
hm_sc(e,d_c(y,5),4); //【メニューのdisplayの処理】
e=r_e('i_id'); //アイコン要素のエレメント
hm_sc(e,d_c(y,6),4); //【メニューホバー用アイコンのdisplayの処理】
}
function r_e(i){return document.getElementById(i);}
function hm_sc(e,v,t){
var d='px';
if(e){//エレメントがundefinedなら処理しない
switch(t){
case 0:e.style.width = v+d;break;
case 1:e.style.fontSize = v+d;break;
case 2:e.style.left = v+d;break;
case 3:e.style.top = v+d;break;
case 4:e.style.display = v?'block':'none';break;
}
}
}
function d_c(y,t){※同じなので省略}
function hm_wxv2(w,x){※同じなので省略}
同じエレメントのwidthとleftを変更する時に何度も「getElementById」すると処理が重複しますので、汎用処理関数を作る時にはエレメントを渡して処理する形で作るのが一般的かと思ったりします。
その理由として、forループの中から呼ぶことを想定した時に、何度も同じIDから同じエレメント取得処理をしてしまう(別に問題があるわけではありませんが…)こともありえるためです。
※こういうプログラム事例でのあるあるネタとして、forループの第二引数に教科書に書いてあるからそのままやってしまいがちな、[for(var i=0;i<a.length;i++)]って毎回値が変化しないのに、何度も何度も同じ配列長取得してるやん!問題があります。(もしかすると好みの問題なのかもしれませんが…)
このネタについては某国のプログラマさんが、[for(var i=0,l=a.length;i<l;i++)]にしたらええんちゃうのん?等とおっしゃってますね。
原森的には、そのfor文で初めて変数iやlを使うとしても、var指定は関数の頭に自分で集めておく方がすっきりするので [var i,l=a.length;for(i=0;i<l;i++)] の方が好きだったりします。…閑話休題。
※備考1:hm_sc関数は引数tからのswitch文で処理を逐次書いていますが、javascript研究の別の記事でswitch文を使わずに単純な記述にするネタを書いていますが、ここではこのままにしておきます。
※備考2:fontSizeは単位がpx以外にいろいろありますが、この記事で紹介したいテーマではないのでpx専用にしています。
※備考3:displayは他にinlineやtableなどいろいろありますが、メインテーマではないのでここではblockとnoneのみにしています。
さらに配列を活用して処理指定部分もデータ化してみる
今回のテーマは多次元配列の活用で処理とデータを分けて汎用関数化することなので、最後に残っている、[hm_c_change(w)]関数からスタイル変更処理を指定している部分もデータ化する、という形に変更してみます。
例えば、
e=r_e('b_id'); //メイン要素のエレメント
hm_sc(e,d_c(y,0),0); //【メイン横幅の処理】
e=r_e('m_id'); //メニュ要素のエレメント
hm_sc(e,d_c(y,1),0); //【メニュー横幅の処理】
hm_sc(e,d_c(y,2),1); //【メニューの文字の処理】
…の
ID名の 'b_id' や 'm_id' や、
変更値を取得する番号と変更するスタイルを指定する番号に対応する
hm_sc(e,d_c(y,1),0) や hm_sc(e,d_c(y,2),1)
の引数値などをデータ化する、ということです。
データは多次元配列を使い、処理部分はforループの形にしてみます。
処理の値を単純な配列にすると…
[
[ 'b_id' , 0 , 0 ],
[ 'm_id' , 0 , 1 ],
[ 'm_id' , 1 , 2 ],
[ 'm_id' , 2 , 3 ],
[ 'm_id' , 3 , 4 ],
[ 'm_id' , 4 , 5 ],
[ 'i_id' , 4 , 6 ]
]
idが重複しているので、エレメント取得処理が重複するので、2次元から3次元配列にしておきます。
[
[ 'b_id' , [ 0 ] , [ 0 ] ],
[ 'm_id' , [ 0 , 1 , 2 , 3 , 4 ] , [ 1 , 2 , 3 , 4 , 5 ] ],
[ 'i_id' , [ 4 ] , [ 6 ] ]
]
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=d_c(0,-1),y=hm_wxv2(w,x),i,e,
f=[
['b_id',[0],[0]],
['m_id',[0,1,2,3,4],[1,2,3,4,5]],
['i_id',[4],[6]]
],
l=f.length,j,k;
for(i=0;i<l;i++){ //id名でループ(例ではl=3)
e=r_e(f[i][0]); //エレメント取得
k=f[i][1].length; //処理の数
for(j=0;j<k;j++)hm_sc(e,d_c(y,f[i][2][j]),f[i][1][j]); //idごとにスタイル変更処理
}
}
function r_e(i){※同じなので省略}
function hm_sc(e,v,t){※同じなので省略}
function d_c(y,t){※同じなので省略}
function hm_wxv2(w,x){※同じなので省略}
配列処理なのでforEachを使うと、for文用の変数を減らしたり、配列のlength取得処理を不要にしたりできます。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=d_c(0,-1),y=hm_wxv2(w,x),e,
f=[ ['b_id',[0],[0]] , ['m_id',[0,1,2,3,4],[1,2,3,4,5]] , ['i_id',[4],[6]] ];
f.forEach(function(v){
e=r_e(v[0]);
v[1].forEach(function(x,j){
hm_sc(e,d_c(y,v[2][j]),x);
});
});
}
function r_e(i){※同じなので省略}
function hm_sc(e,v,t){※同じなので省略}
function d_c(y,t){※同じなので省略}
function hm_wxv2(w,x){※同じなので省略}
forEach入れ子の値について
外ループ1回目 : v = f[0] = ['b_id',[0],[0]]
v[0] = f[0][0] = 'b_id'
v[1] = f[0][1] = [0]
v[2] = f[0][2] = [0]
中ループ1回目(外1) :
x = v[1][0] = 0 / v[2][0] = 0
外ループ2回目 : v = f[1] = ['m_id',[0,1,2,3,4],[1,2,3,4,5]]
v[0] = f[1][0] = 'm_id'
v[1] = f[1][1] = [0,1,2,3,4]
v[2] = f[1][2] = [1,2,3,4,5]
中ループ1回目(外2) :
x = v[1][0] = 0 / v[2][0] = 1
x = v[1][1] = 1 / v[2][1] = 2
x = v[1][2] = 2 / v[2][2] = 3
x = v[1][3] = 3 / v[2][3] = 4
x = v[1][4] = 4 / v[2][4] = 5
外ループ3回目 : v = f[2] = ['i_id',[4],[6]]
v[0] = f[2][0] = 'i_id'
v[1] = f[2][1] = [4]
v[2] = f[2][2] = [6]
中ループ1回目(外3) :
x = v[1][0] = 4 / v[2][0] = 6
今回のプログラムのまとめ
最後に、ページごとに汎用的に使うために、多次元配列fを設定する関数を用意してみました。
引数 w … 取得したウインドウの横幅長さ
function hm_c_change(w){
var x=d_c(0,-1),y=hm_wxv2(w,x),e,f=d_p();
f.forEach(function(v){
e=r_e(v[0]);
v[1].forEach(function(x,j){hm_sc(e,d_c(y,v[2][j]),x);});
});
}
function r_e(i){return document.getElementById(i);}
function hm_sc(e,v,t){
var d='px';
if(e){
switch(t){
case 0:e.style.width = v+d;break;
case 1:e.style.fontSize = v+d;break;
case 2:e.style.left = v+d;break;
case 3:e.style.top = v+d;break;
case 4:e.style.display = v?'block':'none';break;
}
}
}
function hm_wxv2(w,x){
var c=x.indexOf(w),d=[];
if(c==-1){
d=x.concat(w);
d.sort(function(a,b){return b-a;});
c=d.indexOf(w);
}
return c;
}
//以下はページごとに内容が異なる時に値を書き換えるデータ関数
function d_c(y,t){
var x=[1024,800,500],
z=[
[960,760,460,300],
[350,250,200,150],
[16,14,13,12],
[10,10,5,5],
[50,50,50,100],
[1,1,0,0],
[0,0,1,1]
];
return (t<0)?x:z[t][y];
}
function d_p(){
return [
['b_id',[0],[0]] ,
['m_id',[0,1,2,3,4],[1,2,3,4,5]] ,
['i_id',[4],[6]]
];
}
[hm_c_change][r_e][hm_sc][hm_wxv2]はどのページで使う場合も同じなので共通関数用のjsファイルに記述し、[d_c][d_p]はページごとに値が違う場合があるので別のjsファイルに記載する、などの使い方ができるかと思います。(原森はあまり好みではないですが、HTMLのヘッダーに記載する使い方も考えられます)
値を変更したり追加している例
例えば、別のページでは範囲の値を増やし、980を追加している場合は
function d_c(y,t){
var x=[1024,980,800,500],
z=[
[960,860,760,460,300],
[350,300,250,200,150],
[16,15,14,13,12],
[10,10,10,5,5],
[50,50,50,50,100],
[1,1,1,0,0],
[0,0,0,1,1]
];
return (t<0)?x:z[t][y];
}
…のように対応するインデックス番号の位置に値を追加する形になります。
また、例えばページの商品画像(id=g_id)横幅サイズを変更する処理(値は[400,400,200,200])を追加の場合
function d_c(y,t){
var x=[1024,800,500],
z=[
[960,760,460,300],
[350,250,200,150],
[16,14,13,12],
[10,10,5,5],
[50,50,50,100],
[1,1,0,0],
[0,0,1,1],
[400,400,200,200] // ←インデックス番号 = 7
];
return (t<0)?x:z[t][y];
}
function d_p(){
return [
['b_id',[0],[0]] ,
['m_id',[0,1,2,3,4],[1,2,3,4,5]] ,
['i_id',[4],[6]] ,
['g_id',[0],[7]]
];
}
商品画像('g_id')の横幅サイズ(0番)を [400,400,200,200][y] にする、に対応した部分をデータ関数に追加しています。
まとめ
ということで、if~elseで逐次的に処理をたくさん記述するプログラムから、値や処理タイプを多次元配列でデータ化し、各ページで共通に使う汎用処理関数から切り離すことで、他のページで使う時はデータ関数のみ違う値にするだけになるプログラムアイデアについて紹介してみました。
以上、長い記事を最後まで読まれたみなさん、お疲れさまでした。
ページトップへ
おまけ
function hm_sc(e,v,t){
var d='px';
if(e){
switch(t){
case 0:e.style.width = v+d;break;
case 1:e.style.fontSize = v+d;break;
case 2:e.style.left = v+d;break;
case 3:e.style.top = v+d;break;
case 4:e.style.display = v?'block':'none';break;
}
}
}
例えば上記プログラムを以下のような感じで汎用関数化するネタを他の記事で書いてます。
function hm_sc(e,v,t){
c_s(e,(t===4)?(v?'block':'none'):(v+'px'));
}
//スタイル変更汎用関数
function c_s(e,t,v){
e&&e['style'][r_s(t)] = v;
}
//スタイル関連文字列データ関数
function r_s(n){
return ['width','fontSize','left','top','display',''][n];
}