[EffectiveJavaScript輪読会7]配列の反復処理には、for…inループではなく、forループを使おう

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「列挙の実行中にオブジェクトを変更しない」をお届けしました。

[EffectiveJavaScript輪読会7]列挙の実行中にオブジェクトを変更しない
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のシリーズでは、第5章の考察をお届けしました。...

今日は2回目で、 「配列の反復処理には、for…inループではなく、forループを使おう」「配列コンストラクタよりも配列リテラルのほうが好ましい」 を2本まとめてお届けします。

テキスト第5章「配列とディクショナリ」の項目49、52に対応しています。

今日のアジェンダ

  • 配列の反復処理には、for…inループではなく、forループを使おう
  • 配列コンストラクタよりも配列リテラルのほうが好ましい

配列の反復処理には、for…inループではなく、forループを使おう

復習ですが、for in文は、プロパティのキーを、プロトタイプチェーンをさかのぼって取得します。

そして、プロパティのキーは、文字列かSymbol型でした。

なので、このように、配列をfor in文で回すと、まちがった結果を求めてしまいます。

function myFunction7_49_01() {

  const scores = [98, 74, 85, 77, 93, 100, 89];
  let total = 0;
  for (const score in scores) {
    total += score;
  }
  const mean = total / scores.length;
  console.log(mean); //17636.571428571428
}

配列の要素を回すときなどは、カウント変数型のfor文を使いましょう。

function myFunction7_49_02() {

  const scores = [98, 74, 85, 77, 93, 100, 89];
  let total = 0;

  for (let i = 0; i < scores.length; i++) {
    total += scores[i];
  }

  const mean = total / scores.length;
  console.log(mean); //88
}

reduceメソッド

そして、中級を卒業したみなさんでしたら、reduceメソッドを使いこなしていきましょうね。

初期値となる第2引数を設定しておくのがポイントです。(なくても動くのですが、正確です)

function myFunction7_49_03() {

  const scores = [98, 74, 85, 77, 93, 100, 89];
  const total = scores.reduce((acc, cur) => acc + cur,0);
  console.log(total / scores.length); //88
}

配列コンストラクタよりも配列リテラルのほうが好ましい

配列リテラル

メソッドをつかわずに配列を生成する方法は、2種類あります。

中級講座でもご紹介しましたが、「リテラル構文はnew演算子の糖衣構文」です。

積極的にリテラル構文で書いていきましょう。

ちなみに、配列の要素を比べるmap()メソッドをご紹介しておきます。

function myFunction7_52_01() {

  const a = new Array(1, 2, 3, 5, 6);

  //リテラル構文はnew演算子の糖衣構文
  const aLiteral = [1, 2, 3, 4, 5];
  console.log(aLiteral); //[1, 2, 3, 5, 6]

  const boolArray = a.map((element, index) => element === aLiteral[index]);
  console.log(boolArray); //	[ true, true, true, false, false ]

}

コンストラクタ

組み込みオブジェクトのJSONや、GWSのServicesを除く、ほとんどのクラスがコンストラクタを持っています

自作クラスも、かならずコンストラクタをもっています。

今日は、コンストラクタを変更できるような関数を作成してみます。

テキストでは、「引数に別のコンストラクタを渡すと、渡したコンストラクタが実行される」というコードが紹介されていました。

function myFunction7_52_02() {

  console.log(Array); //[Function: Array]
  console.log(String); //[Function: String]

  class Test { }
  console.log(Test); //[Function: Test]

  //コンストラクタを変更する関数
  function f(Array) {
    return new Array(1, 2, 3, 4, 5);
  }

  //引数に別のコンストラクタを渡す
  console.log(f(String)); //{ '0': '1' }

  //Stringオブジェクトのコンストラクタはオブジェクトを生成します
  console.log(new String(1)); //{ '0': '1' }

  console.log(new String(1).valueOf()); //1
  console.log(new String(1).toString()); //1

  //番外編コンストラクタをもっていない
  console.log(JSON); //{}
  console.log(SpreadsheetApp); //{}
}

そして、コンストラクタは、容易に書き換えることができます。

オブジェクトを生成するとき、リテラルが用意されているときは、リテラルをつかいましょう、という理由は、この辺にもつながっているのですね。

function myFunction7_52_03() {

  //コンストラクタはかんたんに書き換えることができる
  Array = String;

  console.log(new Array(1, 2, 3, 4, 5)); //{ '0': '1' }

  console.log(new Array(1, 2, 3, 4, 5).valueOf()); //1
  console.log(new Array(1, 2, 3, 4, 5).toString()); //1

}

Arrayコンストラクタ

Array()コンストラクターは、Arrayオブジェクトを生成するために使用します。

引数に渡された整数の数だけ、空の要素をもつ配列を生成します。(公式リファレンスでは”空の”と書いてますが、これはnullです。)

これまで、Arrayコンストラクタの使い道が、あまりありませんでしたが、fill()メソッドとmap()メソッドの組み合わせで脚光を浴びました。(個人的な感想です)

function myFunction7_52_04() {

  //Arrayコンストラクタはnullを要素にもつ配列を生成する
  const array = new Array(3);
  console.log(array.length); //3

  //fill()メソッドで埋めると使いやすい
  const array2 = new Array(3).fill('');
  console.log(array2); //[ '', '', '' ]

  //map()と組み合わせるとさらに強力に
  const array3 = new Array(3).fill().map((_, index) => index + 1);
  console.log(array3); //[ 1, 2, 3 ]

}

今回の考察

さいごに、コンストラクタについて、考察したコードを貼っておきます。

みなさんの腹落ちに、力添えできると幸いです。

function myFunction7_52_05() {

  //この5つはFunctionオブジェクトからコンストラクタを継承してるんじゃないかな
  console.log(Object); //[Function: Object]
  console.log(Object.prototype.toString.call({})); //[object Object]
  console.log(Object.prototype.toString.call(Object)); //[object Function]
  console.log(Object.prototype); //{}
  console.log(Object.__proto__); //[Function]
  console.log();

  console.log(Date); //[Function: Date]
  console.log(Object.prototype.toString.call(new Date())); //[object Date]
  console.log(Object.prototype.toString.call(Date)); //[object Function]
  console.log(Date.prototype); //{}
  console.log(Date.__proto__); //[Function]
  console.log();

  console.log(String); //[Function: String]
  console.log(Object.prototype.toString.call('a')); //[object String]
  console.log(Object.prototype.toString.call(String)); //[object Function]
  console.log(String.prototype); //{}
  console.log(String.__proto__); //[Function]
  console.log();

  console.log(RegExp); //[Function: RegExp]
  console.log(Object.prototype.toString.call(/.*/)); //[object RegExp]
  console.log(Object.prototype.toString.call(RegExp)); //[object Function]
  console.log(RegExp.prototype); //{}
  console.log(RegExp.__proto__); //[Function]
  console.log();

  //Functionオブジェクトのコンストラクタだけど、プロトタイプがなんで配列リテラルなんだろ?
  console.log(Array); //[Function: Array]
  console.log(Object.prototype.toString.call([])); //[object Array]
  console.log(Object.prototype.toString.call(Array)); //[object Function]
  console.log(Array.prototype); /** [] ←Functionオブジェクトのはずなんだけどな。。。*/
  console.log(Array.__proto__); //[Function]
  console.log(Array.__proto__ === Function.prototype); //true
  console.log();

  //コンストラクタそのもの
  console.log(Function); //[Function: Function]
  console.log(Object.prototype.toString.call(function () { })); //[object Function]
  console.log(Object.prototype.toString.call(Function)); //[object Function]
  console.log(Function.prototype); //[Function]
  console.log(Function.__proto__); //[Function]
  console.log(Function.prototype === Function.__proto__); //true
  console.log();

  //コンストラクタが無いんじゃないかな
  console.log(JSON); //{}
  console.log(JSON.prototype); //undefined
  console.log(Object.prototype.toString.call(JSON)); /** [object JSON]  */
  console.log();

  console.log(SpreadsheetApp); //{}
  console.log(SpreadsheetApp.prototype); //undefined
  console.log(Object.prototype.toString.call(SpreadsheetApp)); /** [object Object] */
  console.log();

  //自作コンストラクタ→自作クラスのコンストラクタは、Functionオブジェクトから継承している(デバッガがループする)
  class Test { }
  console.log(Test); //[Function: Test]
  console.log(Object.prototype.toString.call(new Test())); //[object Object]
  console.log(Object.prototype.toString.call(Test)); //[object Function]
  console.log(Test.prototype); //{} Functionオブジェクトじゃないかな なぜなら
  console.log(Test.__proto__); //[Function] だから
  console.log(Test.prototype.__proto__); //{} Objectオブジェクト Functionオブジェクトの継承元
  console.log(Test.prototype.__proto__.__proto__); //null Object.prototype.__proto__と同じ意味
  console.log();

}

まとめ

以上で、「配列の反復処理には、for…inループではなく、forループを使おう」と、「配列コンストラクタよりも配列リテラルのほうが好ましい」を、2本まとめてお届けしました。

次回は、「複数のループよりも反復メソッドが好ましい」 をお届けします。

参考資料

Array() コンストラクター MDN

このシリーズの目次

タイトルとURLをコピーしました