どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「列挙の実行中にオブジェクトを変更しない」をお届けしました。
今日は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本まとめてお届けしました。
次回は、「複数のループよりも反復メソッドが好ましい」 をお届けします。