4.4 配列と引数の渡し方

(1/1)
箱の中身を当てるゲームのイラスト(女性)
JavaScriptでは関数の引数として配列を渡すこともできる。

引数は、見た目の上では、Number型やString型のようなプリミティブ型の場合は参照渡しで、配列などオブジェクト型は値渡しで渡される。(JavaScriptの引数渡しは、他の言語の参照渡しや値渡しとは異なる。)

目次

サンプル・プログラム

平均を求める

合計点・平均点
関数の引数として配列を渡すこともできる。
例として、テストの平均点を求めるプログラム "average1.html" を作ってみよう。
左図がプログラムの実行例である。

 115:     //点数表
 116:     let scores = [75, 68, 81, 90, 57, 52, 96, 72, 68, 72];

まず、メイン・プログラム側で、10人のテストの点数を配列 scores に代入しておく。
この配列をユーザー定義関数 dispScoreTable に渡し、平均点を求めるとともに、点数一覧表を作成する。

  36: /**
  37:  * 点数表を作成し,画面に表示する.
  38:  * @param   Array scores 点数表
  39:  * @return  なし
  40: */
  41: function dispScoreTable(scores) {
  42:     //エラーメッセージをクリア
  43:     document.getElementById('error').innerHTML = '';
  44: 
  45:     //配列のバリデーション
  46:     if (! Array.isArray(scores)) {
  47:         //エラーを表示
  48:         document.getElementById('error').innerHTML = 'エラー:正しい点数表を用意してください.';
  49: 
  50:     //点数表を作成
  51:     } else {
  52:         //点数合計と要素数をカウント
  53:         let sum = 0;    //合計点数
  54:         for (let i = 0i < scores.lengthi++) {
  55:             let a = parseFloat(scores[i]);
  56:             //数字以外が含まれていたらNaNを代入して脱出
  57:             if (Number.isNaN(a)) {
  58:                 n = sum = NaN;
  59:                 return;
  60:             }
  61:             sum +a;
  62:         }
  63: 
  64:         //点数表の1行目
  65:         html =`
  66: <tr>
  67: <th>出席番号</th>
  68: <th>点数</th>
  69: </tr>
  70: `;
  71:         //計算結果のバリデーション
  72:         if (Number.isNaN(sum)) {
  73:             //エラーを表示
  74:             document.getElementById('error').innerHTML = 'エラー:正しい点数表を用意してください.';
  75: 
  76:         //点数表を作成
  77:         } else {
  78:             for (let i = 0i < scores.lengthi++) {
  79:                 html +=`
  80: <tr>
  81: <td>${i + 1}</td>
  82: <td>${scores[i]}</td>
  83: </tr>
  84: `;
  85:             }
  86:             html +=`
  87: <tr>
  88: <td>合計点</td>
  89: <td>${sum}</td>
  90: </tr>
  91: <tr>
  92: <td>平均点</td>
  93: <td>${sum / scores.length}</td>
  94: </tr>
  95: `;
  96:             //結果を表示
  97:             document.getElementById('scoreTable').innerHTML = html;
  98:         }
  99:     }
 100: }

引数として渡された配列は、これまで同様 score[i] と書くことで、これまで同様、目的の要素を参照することができる。
配列に格納されている要素の数は、length プロパティを使って取得できる。
要素の数だけ forループを回し、合計点と平均点を計算する。
あとは、点数表をTABLEタグとして組み立てる。

引数の参照渡し

さて、関数に渡された引数を、関数内で変更したらどうなるだろうか。

  19: <script>
  20: // ユーザー定義関数 ========================================================
  21: /**
  22:  * 引数に+1する(その1)
  23:  * @param   Number a
  24:  * @return  Number a + 1
  25: */
  26: function plus1(a) {
  27:     let b = a + 1;
  28:     return b;
  29: }
  30: 
  31: /**
  32:  * 引数に+1する(その2)
  33:  * @param   Number a
  34:  * @return  Number a + 1
  35: */
  36: function plus2(a) {
  37:     a = a + 1;
  38:     document.getElementById('var3').innerHTML = 'a = ' + a.toString();
  39:     return a;
  40: }
  41: 
  42: // メイン・プログラム ======================================================
  43: window.onload = function() {
  44:     let a = 1;
  45:     document.getElementById('var1').innerHTML = 'A = ' + a.toString();
  46:     plus1(a);
  47:     document.getElementById('var2').innerHTML = 'A = ' + a.toString();
  48:     plus2(a);
  49:     document.getElementById('var4').innerHTML = 'A = ' + a.toString();
  50: }
  51: </script>

ユーザー定義関数 plus1 は、渡された引数に1を加算し、関数内ローカル変数bに代入する。結果は次のようになる。
A = 1
A = 1
a = 2
A = 1
つまり、メイン・プログラム側の変数aは、plus1 の実行の前後で変わらない。

ユーザー定義関数 plus2 は、渡された引数を、関数内でそのまま1を加算する。関数内の変数aは2になるが、メイン・プログラム側の変数aは plus1 の実行の前後で変わらない。

このように、JavaScriptでは、関数に渡された引数は、関数内ローカル変数として振る舞う。これを引数の値渡しと呼ぶ。

引数の値渡し

一方、関数に配列を引数として渡すと、参照渡しとならない。プログラム "average1.html" の実行結果は次のようになる。
A = 1,2,3,4,5
A = 1,2,3,4,5
a = 1,2,3,5,5
A = 1,2,3,5,5

  19: <script>
  20: // ユーザー定義関数 ========================================================
  21: /**
  22:  * 配列引数に+1する(その1)
  23:  * @param   Array  a 配列
  24:  * @param   Number n 要素番号
  25:  * @return  Number a + 1
  26: */
  27: function plus3(a, n) {
  28:     let b = a[n+ 1;
  29:     return b;
  30: }
  31: 
  32: /**
  33:  * 配列引数に+1する(その2)
  34:  * @param   Array  a 配列
  35:  * @param   Number n 要素番号
  36:  * @return  Number a + 1
  37: */
  38: function plus4(a, n) {
  39:     a[n] = a[n+ 1;
  40:     document.getElementById('var4').innerHTML = 'a = ' + a.toString();
  41:     return a[n];
  42: }
  43: 
  44: // メイン・プログラム ======================================================
  45: window.onload = function() {
  46:     let a = [1, 2, 3, 4, 5];
  47:     let n = 3;
  48:     document.getElementById('var1').innerHTML = 'A = ' + a.toString();
  49:     plus3(a, n);
  50:     document.getElementById('var3').innerHTML = 'A = ' + a.toString();
  51:     plus4(a, n);
  52:     document.getElementById('var5').innerHTML = 'A = ' + a.toString();
  53: }
  54: </script>

ユーザー定義関数 plus1 は、渡された引数(配列)an 番目の要素を1を加算し、関数内ローカル変数bに代入する。メイン・プログラム側の配列 a は、plus3 の実行の前後で変わらない。

ユーザー定義関数 plus は、渡された引数(配列)an 番目の要素を、関数内でそのまま1を加算する。すると、メイン・プログラム側の配列aは1を加算された要素に変化する。

このように配列を引数として渡す場合、元の配列をそのまま処理していることになる。これを引数の参照渡しと呼ぶ。

配列をシャフルする

トランプなどのカード型ゲームを作るとき、あらかじめカードを配列に代入しておき、その配列をシャフルすることを行う。JavaScriptでは配列が参照渡しという性質を使って、渡した配列をシャフルする関数を簡単に作ることができる。

  36: /**
  37:  * 指定した配列をシャフルする.
  38:  * @param   Array arr シャフルしたい配列
  39:  * @return  なし
  40: */
  41: function shuffle(arr) {
  42:     for (let i = arr.lengthi > 1i--) {
  43:         let k = Math.floor(Math.random() * i);
  44:         [arr[k], arr[i - 1]] = [arr[i - 1], arr[k]];
  45:     }
  46: }

配列をシャフルする
シャフルの方法は、左図のように、配列の前後の要素を入れ替える操作を、ランダムに要素の回数だけを行う。
要素の数は lengthプロパティによって取得できる。
ランダムに要素を取り出すには、Math.random 関数を使う。戻り値が浮動小数なので、Math.floor 関数を使い、小数点以下を切り捨てる。

読みやすいプログラム:コメント

文字化けした文章を見る人のイラスト
JavaScriptにおけるコメントの書き方は、「2.10 演算の優先順位、コメント - コメント」で紹介した。

かつて、アセンブリやC言語のようなプログラミング言語では、関数や変数名が省略形であったり、独特なプログラミング記述法があったために、コメントは不可欠であった。
だが、今日のJavaScriptのような、いわゆる高級言語では、仕様設計に記した内容をそのままプログラムとして書くことができる。
プログラムを読めば分かることをコメントに書くのは二度手間であるし、バージョンアップ時にいずれか一方の更新を忘れるとバグの温床になることから、コメントは必要最小限にとどめるべきというのが、今風の読みやすいプログラムとなる。

●コメントに記載する内容
  1. プログラム使用上の注意、制約条件など。
  2. プログラム使用にあたって準備すること、実施することなど。
  3. 参照しているライブラリ、フレームワーク、APIなどの情報。
  4. プログラムの著作権情報。
  5. コメントで補足しないと分からないような複雑な箇所。
●コメントに記載する必要がないもの
  1. 変数やプロパティの1つ1つに対する解説。
  2. プログラムを読めば分かる内容。ただし、読み手のスキルに応じてコメントを付加すること。
  3. プログラムの更新履歴。ただし、バージョン管理システムを利用していない場合はコメントとして記述すること。
本連載では、初心者を対象にしている関係で、プログラムを読めば分かる内容でもコメントを記載している。また、GitHub のようなバージョン管理システムを使っていないので、ファイルの末尾に更新履歴を付け加えている。 本連載では、クラスや関数の冒頭に、次のような書式で解説を付けている。
/**
* (1行目)機能概要【必須】
* (2行目)機能詳細,制約事項など【省略可能】
* @param{空白}変数の型{空白}変数名{空白}内容‥‥引数1の説明【省略可能】
* @param{空白}変数の型{空白}変数名{空白}内容‥‥引数2の説明【省略可能】
* (1行に1引数を記述する.)
* @return{空白}変数の型‥‥戻り値の説明【省略可能】
*/
たとえば、次のようなコメントである。
/**
* 点数表を作成し,画面に表示する.
* @param Array scores 点数表
* @return なし
*/
コメントからJavaScriptのドキュメントを自動作成する JSDoc に近い書式であるが、他言語でも利用できるよう汎用性を持たせており、当サイトの構築ツール(非公開)を使って下記のような一覧表を作成できるようになっている。
average1.html 関数/メソッド一覧
関数/メソッド 機能 詳細
getLastModified ファイル更新日を返す
dispScoreTable 点数表を作成し,画面に表示する.

読みやすいプログラム:引数の変更・上書きはしない

友達にプレゼントを贈っている人のイラスト
他のプログラミング言語にも値渡し参照渡しがあるのだが、JavaScriptの引数は、これらの言語で言うところの値渡し参照渡しとは異なる。
JavaScriptでは、同じ変数に対して代入演算子 = が実行されると、見た目は変数の内容を上書きするのだが、内部的には別領域に値を格納している。
let a = 1;
a = 2;
上のようなプログラムの場合、最初の領域に1が代入され、2行目には別領域が用意され、そこへ2が代入される。そして、変数 a の参照先を2番目の領域に更新する。
これが引数にしたときにも起きるので、じつは参照渡しに近いことをやっているのだが、結果的に値渡ししているように見える。

配列の場合は、配列が格納されている領域のポインタ(のようなもの)を渡す。引数で渡すときには、あらたな領域に代入されるのだが、そこには同じ値のポインタ(のようなもの)を代入するので、値渡しのように要素の値を変えることができる。

つまり、JavaScriptの場合、プリミティブ型でもオブジェクト型でも、仕様上は同じ方法で引数を渡している。これを共有渡しと呼ぶ方もいるが、正式な呼称はないようだ。
また、内部的にどのような渡し方をしているかはブラウザの実装によるので、実際に同じ方法で渡しているかどうかは分からない。
このあたりの事情は、@yuta0801氏の記事「JavaScriptに参照渡し/値渡しなど存在しない」に詳しい。

このようにJavaScriptの引数には癖があるため、引数の変更・上書きはしないことが読みやすいプログラムとなる。
(この項おわり)
header