
目次
サンプル・プログラム
employeeList1.html | 社員名簿(配列形式) |
employeeList2.html | 社員名簿(オブジェクト形式) |
employeeList3.html | 社員名簿(構造変更可能) |
arrayCase1.html | 条件分岐を配列にする |
社員名簿(配列形式)
今期売上は営業職のみに登録されている点、社員番号091001の成田祥三さんは所属部署を兼務している点に注意してほしい。

24: //社員名簿(配列形式)
25: const EMPLOYEE_LIST = [
26: ['094011', '相馬', '豊司', '男', '1994/4/1', '営業部', '営業', 52600],
27: ['099004', '原口', '奈緒子', '女', '1999/7/5', '人事部', '事務', -1],
28: ['012023', '足立', '俊康', '男', '2012/3/12', 'システム営業部', '技術', -1],
29: ['004002', '畠山', '忠秋', '男', '2004/4/1', '経理部', '事務', -1],
30: ['013010', '島崎', '晴生', '男', '2013/4/1', '営業部', '営業', 23800],
31: ['096008', '丹羽', '麻樹', '女', '1996/4/1', '営業部', '営業', 31600],
32: ['096011', '谷本', '房実', '女', '1996/4/1', '経理部', '事務', -1],
33: ['020017', '深沢', 'つばさ', '男', '2020/4/1', 'システム営業部', '営業', 42800],
34: ['007005', '川島', '武久', '男', '2007/4/1', '営業部', '営業', 32700],
35: ['091001', '成田', '祥三', '男', '1991/4/1', '開発部,システム営業部', '技術', -1]
36: ];
所属の兼務は、カンマ区切りにして1つのテキストにした。
50: /**
51: * 社員名簿を一覧表示する.
52: * @param Array persons 社員名簿(2次元配列)
53: * @return なし
54: */
55: function dispTable1(persons) {
56: //エラーメッセージをクリア
57: document.getElementById('error').innerHTML = '';
58:
59: //配列のバリデーション
60: if (! Array.isArray(persons)) {
61: //エラーを表示
62: document.getElementById('error').innerHTML = 'エラー:正しい社員名簿を用意してください.';
63:
64: } else {
65: //売上合計と営業職を集計
66: let n = 0; //営業職の人数
67: let sum = 0; //合計売上
68: for (let i = 0; i < persons.length; i++) {
69: let a = parseFloat(persons[i][7]);
70: //数字以外が含まれていたらNaNを代入して脱出
71: if (Number.isNaN(a)) {
72: n = sum = NaN;
73: return;
74: //合計値,人数に加算
75: } else if (a >= 0) {
76: n++;
77: sum += a;
78: }
79: }
80:
81: //社員名簿の1行目
82: html =`
83: <tr>
84: <th>社員番号</th><th>姓</th><th>名</th><th>性別</th><th>入社日</th>
85: <th>所属部署</th><th>職掌</th><th>今期売上<br>(千円)</th>
86: </tr>
87: `;
88: for (let i = 0; i < persons.length; i++) {
89: html += "<tr>\n";
90: //社員番号~職掌
91: for (let j = 0; j < 7; j++) {
92: html += '<td>' + persons[i][j] + '</td>\n';
93: }
94: //売上
95: if (EMPLOYEE_LIST[i][7] >= 0) {
96: let m = persons[i][7].toLocaleString();
97: html += '<td class="num">' + m + '</td>\n';
98: } else {
99: html += '<td> </td>\n';
100: }
101: }
102: html +=`
103: <tr>
104: <td colspan="7">売上合計</td>
105: <td class="num">${sum.toLocaleString()}</td>
106: </tr>
107: <tr>
108: <td colspan="7">売上平均</td>
109: <td class="num">${(sum / n).toLocaleString()}</td>
110: </tr>
111: `;
112: //結果を表示
113: document.getElementById('tableEmployeeList').innerHTML = html;
114: }
115: }

名簿の表示はできたものの、データを2次利用しようとすると問題が出てくる。
まず、今期売上がない職種に -1 を代入しているが、売上に対して何か統計処理を仕様とすると、いちいち -1 かどうかをチェックしなければならない。NaN を代入しておくという方法もあるが、そもそも属性として売上をもっていない職種にデータ項目を与えるべきだろうか。
もう1つは兼務者である。絞り込み機能をつけて "システム営業部" で絞り込もうとすると、兼務者の絞り込みに一工夫必要である。
さらに、1つのデータ項目に2つ以上のデータが入るのは所属だけではないだろう。夫婦別姓制度の関心が高まっている時代である。旧姓登録のために、姓を複数登録できるようにした方がいいかもしれない。
社員名簿(オブジェクト形式)
社員名簿を下表のように考え直す。

これをプログラムにしたものが "employeeList2.html" である。
24: //社員名簿(オブジェクト形式)
25: const EMPLOYEE_LIST = [
26: {
27: number: '094011',
28: last_name: '相馬',
29: first_name: '豊司',
30: sex: '男',
31: hire: '1994/4/1',
32: department: '営業部',
33: duties: '営業',
34: sales: 52600
35: }, {
36: number: '099004',
37: last_name: '原口',
38: first_name: '奈緒子',
39: sex: '女',
40: hire: '1999/7/5',
41: department: '人事部',
42: duties: '事務'
43: }, {
44: number: '012023',
45: last_name: '足立',
46: first_name: '俊康',
47: sex: '男',
48: hire: '2012/3/12',
49: department: 'システム営業部',
50: duties: '技術'
51: }, {
52: number: '004002',
53: last_name: '畠山',
54: first_name: '忠秋',
55: sex: '男',
56: hire: '2004/4/1',
57: department: '経理部',
58: duties: '事務'
59: }, {
60: number: '013010',
61: last_name: '島崎',
62: first_name: '晴生',
63: sex: '男',
64: hire: '2013/4/1',
65: department: '営業部',
66: duties: '営業',
67: sales: 23800
68: }, {
69: number: '096008',
70: last_name: '丹羽',
71: first_name: '麻樹',
72: sex: '女',
73: hire: '1996/4/1',
74: department: '営業部',
75: duties: '営業',
76: sales: 31600
77: }, {
78: number: '096011',
79: last_name: '谷本',
80: first_name: '房実',
81: sex: '女',
82: hire: '1996/4/1',
83: department: '経理部',
84: duties: '事務',
85: }, {
86: number: '020017',
87: last_name: '深沢',
88: first_name: 'つばさ',
89: sex: '男',
90: hire: '2020/4/1',
91: department: 'システム営業部',
92: duties: '営業',
93: sales: 42800
94: }, {
95: number: '007005',
96: last_name: '川島',
97: first_name: '武久',
98: sex: '男',
99: hire: '2007/4/1',
100: department: '営業部',
101: duties: '営業',
102: sales: 32700
103: }, {
104: number: '091001',
105: last_name: '成田',
106: first_name: '祥三',
107: sex: '男',
108: hire: '1991/4/1',
109: department: ['開発部', 'システム営業部'],
110: duties: '技術'
111: }];
let/const 変数名 = {ここでは、社員1人1人が1つのオブジェクトになっており、それを配列 EMPLOYEE_LIST に代入している。
プロパティ1: 値1,
プロパティ2: 値2,
‥‥
プロパティn: 値
}
const EMPLOYEE_LIST = [今期売上を持たない社員には、プロパティsales を代入しない。
{ 社員1のオブジェクト},
{ 社員2のオブジェクト},
‥‥
{ 社員nのオブジェクト},
];
兼務者については、プロパティ department に配列を代入している。

このような配列やオブジェクトを、とくに JSON(JavaScript Object Notation)形式と呼ぶ。詳しくは「6.5 ファイル・アクセス、同期・非同期、JSON」で説明する。
156: /**
157: * 社員名簿を作成
158: * @param Array EMPLOYEE_LIST 社員名簿(オブジェクト)
159: * @return String TABLEタグ
160: */
161: function dispTable2(persons) {
162: //エラーメッセージをクリア
163: document.getElementById('error').innerHTML = '';
164:
165: //配列のバリデーション
166: if (! Array.isArray(persons)) {
167: //エラーを表示
168: document.getElementById('error').innerHTML = 'エラー:正しい社員名簿を用意してください.';
169:
170: //社員名簿を作成
171: } else {
172: //社員名簿の1行目
173: html =`
174: <tr>
175: <th>社員番号</th><th>姓</th><th>名</th><th>性別</th><th>入社日</th>
176: <th>所属部署</th><th>職掌</th><th>今期売上<br>(千円)</th>
177: </tr>
178: `;
179: let sum = 0;
180: let n = 0;
181: for (let i = 0; i < persons.length; i++) {
182: html += '<tr>';
183: html += makeColumn(persons[i], 'number');
184: html += makeColumn(persons[i], 'last_name');
185: html += makeColumn(persons[i], 'first_name');
186: html += makeColumn(persons[i], 'sex');
187: html += makeColumn(persons[i], 'hire');
188: html += makeColumn(persons[i], 'department');
189: html += makeColumn(persons[i], 'duties');
190: html += makeColumn(persons[i], 'sales');
191: html += '</tr>';
192:
193: //売上
194: if (typeof persons[i].sales !== 'undefined') {
195: sum += parseFloat(persons[i].sales);
196: n++;
197: }
198: }
199: html +=`
200: <tr>
201: <td colspan="7">売上合計</td>
202: <td class="num">${sum.toLocaleString()}</td>
203: </tr>
204: <tr>
205: <td colspan="7">売上平均</td>
206: <td class="num">${(sum / n).toLocaleString()}</td>
207: </tr>
208: `;
209: //結果を表示
210: document.getElementById('tableEmployeeList').innerHTML = html;
211: }
212: }
125: /**
126: * 社員名簿を作成:1つのカラム
127: * @param Object obj 社員1名分のオブジェクト
128: * @param String key プロパティ名
129: * @return String 1つ分のtdタグ
130: */
131: function makeColumn(obj, key) {
132: let html = '';
133:
134: //未定義
135: if (typeof obj[key] == 'undefined') {
136: html = '<td> </td>';
137: //売上
138: } else if (key == 'sales') {
139: html += '<td class="num">' + obj[key].toLocaleString() + '</td>';
140: //配列
141: } else if (Array.isArray(obj[key])) {
142: html += '<td>';
143: for (let i = 0; i < obj[key].length; i++) {
144: if (i > 0) html += '<br />';
145: html += obj[key][i].toString();
146: }
147: html += '</td>';
148: //その他
149: } else {
150: html += '<td>' + obj[key].toString() + '</td>';
151: }
152:
153: return html;
154: }
この関数を拡充すれば、複雑なデータ構造の名簿でも扱うことができるようになる。
forEachループ
24: //社員名簿(オブジェクト配列)【変更可能】
25: //冒頭行は必須.属性と値は自由に追加・変更・削除できる.salesは売上合計対象.
26: const EMPLOYEE_LIST = [
27: //--冒頭行は見出し
28: {
29: number: '社員番号',
30: last_name: '姓',
31: first_name: '名',
32: sex: '性別',
33: hire: '入社日',
34: department: '所属部署',
35: duties: '職掌',
36: sales: '今期売上<br>(千円)'
37: //--以下が名簿本体
38: }, {
39: number: '094011',
40: last_name: '相馬',
41: first_name: '豊司',
42: sex: '男',
43: hire: '1994/4/1',
44: department: '営業部',
45: duties: '営業',
46: sales: 52600
47: }, {
48: number: '099004',
49: last_name: '原口',
50: first_name: '奈緒子',
51: sex: '女',
52: hire: '1999/7/5',
53: department: '人事部',
54: duties: '事務'
55: }, {
56: number: '012023',
57: last_name: '足立',
58: first_name: '俊康',
59: sex: '男',
60: hire: '2012/3/12',
61: department: 'システム営業部',
62: duties: '技術'
63: }, {
64: number: '004002',
65: last_name: '畠山',
66: first_name: '忠秋',
67: sex: '男',
68: hire: '2004/4/1',
69: department: '経理部',
70: duties: '事務'
71: }, {
72: number: '013010',
73: last_name: '島崎',
74: first_name: '晴生',
75: sex: '男',
76: hire: '2013/4/1',
77: department: '営業部',
78: duties: '営業',
79: sales: 23800
80: }, {
81: number: '096008',
82: last_name: '丹羽',
83: first_name: '麻樹',
84: sex: '女',
85: hire: '1996/4/1',
86: department: '営業部',
87: duties: '営業',
88: sales: 31600
89: }, {
90: number: '096011',
91: last_name: '谷本',
92: first_name: '房実',
93: sex: '女',
94: hire: '1996/4/1',
95: department: '経理部',
96: duties: '事務',
97: }, {
98: number: '020017',
99: last_name: '深沢',
100: first_name: 'つばさ',
101: sex: '男',
102: hire: '2020/4/1',
103: department: 'システム営業部',
104: duties: '営業',
105: sales: 42800
106: }, {
107: number: '007005',
108: last_name: '川島',
109: first_name: '武久',
110: sex: '男',
111: hire: '2007/4/1',
112: department: '営業部',
113: duties: '営業',
114: sales: 32700
115: }, {
116: number: '091001',
117: last_name: '成田',
118: first_name: '祥三',
119: sex: '男',
120: hire: '1991/4/1',
121: department: ['開発部', 'システム営業部'],
122: duties: '技術'
123: }];
137: /**
138: * 社員名簿から属性一覧を取得する.
139: * @param Object header 社員名簿の冒頭行
140: * @return Array 属性一覧
141: */
142: function getProperties(header) {
143: let properties = Object();
144: for (const property in header) {
145: properties[property] = header[property];
146: }
147: return properties;
148: }
forループの中が "property in header" という書き方になっているが、これは、オブジェクト header にある属性1つ1つに対してループ処理を行うことを意味する。属性値は property に格納する。
この for...inループを使うことで、ループカウンタを意識しなくても、オブジェクトの全要素に対して漏れなくループ処理を行うことができる。
181: /**
182: * 社員名簿をTABLEタグの形で返す.
183: * @param Array persons 社員名簿(オブジェクト配列)
184: * @return String TABLEタグ
185: */
186: function getTable3(persons) {
187: let sum = 0;
188: let n = 0;
189: let properties = Object();
190: let html = '';
191: persons.forEach(function(person, i) {
192: //冒頭行
193: if (i === 0) {
194: //社員名簿の属性一覧を取得する.
195: properties = getProperties(person);
196: //見出しを作成する.
197: html += '<tr>\n';
198: for (const property in properties) {
199: html += '<th>' + properties[property] + '</th>\n';
200: }
201: html += '</tr>\n';
202: //名簿を作成する.
203: } else {
204: html += '<tr>\n';
205: for (const property in properties) {
206: html += makeColumn(person, property);
207: }
208: html += '</tr>\n';
209: //売上合計に加算する.
210: if (typeof person.sales !== 'undefined') {
211: sum += parseFloat(person.sales);
212: n++;
213: }
214: }
215: });
216:
217: //売上合計を表示する.
218: html +=`
219: <tr>
220: <td colspan="7">売上合計</td>
221: <td class="num">${sum.toLocaleString()}</td>
222: </tr>
223: <tr>
224: <td colspan="7">売上平均</td>
225: <td class="num">${(sum / n).toLocaleString()}</td>
226: </tr>
227: `;
228: return html;
229: }
forEachメソッドの引数にはコールバック関数を記述する。コールバック関数の第1引数には要素の値が、第2引数には要素のインデックスが、第3引数には操作されている配列が入る。これらの引数は省略可能であるが、ここでは第1引数と第2引数を利用する。
if〜else文を使い、冒頭行の時は見出しを、それ以外は名簿の行としてHTMLタグを生成していく。前述の getProperties関数で取得した属性を列として一覧表を作成する。
こうして、社員数(行数)や属性数(列数)を制約するようなカウンタ変数を排除することで、名簿の構造(オブジェクトの構造)は自在に変更できるようになり、関数 getTable3 の見通しも良くなった。

for...in と forEachループを利用することで、行・列の数を意識する必要がなくなった。つまり、列を自在に増やしたり減らしたりできるし、行数(人数)の制約もなくなった。
オブジェクト配列 EMPLOYEE_LIST の冒頭に用意した属性一覧に含まれていれば、その順序で社員名簿を一覧表示することができる。試しに変更してみてほしい。
読みやすいプログラム:ループ処理

ループ条件を1箇所に集めることができるという観点では、whileループよりforループを使うといい。
ただし、forループは開始/終了条件のバグがあっても走らせてみないとわからないので、配列全体に一斉に同じ作用をさせるなら、今回紹介した forEachメソッドを使おう。元の配列を残して新しい配列を生成したいような場合は、「5.3 インスタンス」で紹介する mapメソッドを使って簡明に書くことができる。
オブジェクトに対してループ処理を行う場合は、今回紹介した for...inループを使おう。全要素に対して漏れなくループ処理を適用できる。

なお、JavaScriptのループ処理は同期していることが前提である。ループ処理中の配列に対して非同期処理で変化が加わるようなことは避けること。
読みやすいプログラム:条件分岐を配列にする
21: /**
22: * 判定と画面表示
23: */
24: function switchCase1() {
25: //変数宣言
26: let month = document.getElementById('month').value; //月
27: month = parseInt(month, 10); //10進整数化
28: let ret;
29:
30: //月の大小判定
31: switch (month) {
32: case 1:
33: case 3:
34: case 5:
35: case 7:
36: case 8:
37: case 10:
38: case 12:
39: ret = '大の月';
40: break;
41: case 2:
42: case 4:
43: case 6:
44: case 9:
45: case 11:
46: ret = '小の月';
47: break;
48: default:
49: ret = 'エラー:1以上12以下の整数を入力してください';
50: break;
51:
52: }
53:
54: //結果を表示する
55: document.getElementById('ret').innerHTML = ret;
56: }
21: /**
22: * 判定と画面表示
23: */
24: function arrayCase1() {
25: //月の大小判定の配列;0月はundefined
26: const months = [undefined, '大の月', '小の月', '大の月', '小の月', '大の月', '小の月', '大の月', '大の月', '小の月', '大の月', '小の月', '大の月'];
27:
28: //変数宣言
29: let month = document.getElementById('month').value; //月
30: month = parseInt(month, 10); //10進整数化
31:
32: //月の大小判定
33: let ret = months.find(function (element, index) { return index === month });
34: if (ret == undefined) ret = 'エラー:1以上12以下の整数を入力してください';
35:
36: //結果を表示する
37: document.getElementById('ret').innerHTML = ret;
38: }
ここでは、添字は月の数字であるから、前述の switch~case文で実行したのと同じ結果が得られる。

なお、このプログラムのように配列 months に代入すると、添字は0からスタートで、1, 2, 3‥‥と増えてゆく。0月はあり得ないので、要素に undefined を代入するようにした。また、-1以下の添字や、13以上の添字を findメソッドで探そうとすると undefined が返ることから、undefined が返ってきたらエラーメッセージを表示するようにした。

このように、列挙型の条件分岐は配列とfindメソッドを使うことで読みやすいプログラムになる。
コラム:オブジェクトとは


ジェンダー平等というものの、男性と女性とでは備える属性が微妙に異なる。また、年齢や学歴によって付加される属性が変わってくる。

何かのデータの集合体を扱おうとする時、データ項目を整理してExcel表に収めようとするのではなく、そのデータ集合体の属性を忠実に表そうとするのがオブジェクト指向である。

今回、JSON形式について簡単に触れたが、じつは JSON形式はリレーショナルデータベースのテーブル構造に対応させることができる。配列が行で、プロパティが列に対応すると考えてほしい。
JavaScriptは直接データベースを扱うことはできないが、「第7章 WebAPIとjQuery」で紹介するが、データベースからJSON形式でデータを出力するAPIを用意すれば、データベース処理に関わることができる。
今回は、配列とオブジェクトを比較することで、JavaScriptのオブジェクトの仕組みを学んでいく。今回はプロパティまでで、メソッドについては次回以降に譲る。