反省点

作成日 : 1999-12-05
最終更新日 :

反省点をあげる。

var 宣言忘れ

まず、var 宣言をしなかったために、変数の名前空間が確保できず、 大変なことになってしまった。 var 宣言をしなくても変数が使えるというのは確かにその通りなのだが、var宣言は その関数のブロック内で有効になる、という意味が込められていることをつい最近(1999-12-05 のとき)知った。 これは、継子立てのところで学んだ経験である。

さらに、var 宣言のみではなく、const 宣言や let 宣言も使える。 これらを積極的に活用すれば、誤りが減るのではないかと思う。(2020-03-09)

+ 演算子の結果予測

それから、+演算子が文字列の連結になってしまう場面が予測できなかったこと。 予測できないと思いがけないエラーになる。 たとえば、k = i + j; とある文があり、i = 1, j = 2 ならば k = 3 を期待する。 ところがこの計算を行ってみたら k = 12 となってしまった。この理由がわからず、悩んだ。 12 としても数字としての意味があるのが落とし穴だ。 種を明かせば、i も j も文字列であったために、+ 演算子は文字列連結として働いてしまった、というのが答だ。 1999 年現在は、怪しいところはみな eval()をつけることにしていた。

2020 年に振り返ると、eval() にはセキュリティが保てなくなる問題や処理時間が長くなるという問題がある。 + 演算子で予期せぬ結果が出てしまうことがないようにするには、オブジェクトの種類を明らかにするようにすべきだろう。 特に、Web から入力された場合では、parseInt や parseFloat などを明示的に使って、 フィールドから得られた文字列を逐次数値に変換するなどの方法が必要だ。 もっといえば、TypeScript のような、より型の厳しい言語を使うしかないのだろう。(2020-03-09)

正規表現

正規表現は面白い。使い方の一つを示す。 JavaScript は機種に依存しない書き方ができるはずなのだが、実際には機種依存性がある。 端的には、改行コードの違いとなって現れる。

詳しくは、古籏さんらの「大全」の本に譲るが、UNIXとMacとDOSとすべてで、改行コードが異なる。 これらを吸収すべく、正規表現を使った。なかなか便利だ。 ソースは前の「回帰分析」の章に載せているので御覧いただければ幸いだ。 "\r\n"から"\r"のみか"\n"のみかをマッチングメソッドによって判定する。 マッチングした改行コードは保存しておき、出力として複数行が必要ならば、 joinメソッドの引数に保存した改行コードを指定すればすむ。

もう一つは、一行にある、空白またはタブで区切られた項目の分割だ。 /[ \t]+/というコードを覚えただけで、世界が明るくなった。 また、正規表現は、特定のメソッドしか使えない、ということではないこともわかった。 実際に私は match メソッドと replace メソッドにしか使えないと思っていたが、 split メソッドにもこの正規表現が使えることがわかった。うれしい。

インラインのイベントハンドラの使用の低減

つい最近まで、ボタンをクリックすると関数 do_xyz によるアクションを起こすという処理を次のように書いてきた。

JavaScript ファイル

// xyz.js
function do_xyz() {
  'use strict';
  alert("do_xyz");
}

HTML ファイル

<script src="xyz.js">
<input type="button" id="xyz" value="実行" onclick="do_xyz()">

インラインのイベントハンドラと呼ばれるこの書き方は、現在では避けるべきものであるとされる。その理由は、 入門 モダン JavaScriptによれば次のとおりである:

現在は、W3C DOM レベル 2 仕様書による addEventListener() メソッドを作成するのが望ましい。 私の場合、実際には前掲書「入門 モダン JavaScript」により定義されたユーティリティを用いて、 これはかなりの個所で間接的に使っている。具体的には、ユーティリティをおさめたファイル utilities.js により定義された U オブジェクトを使って次のように記す:

JavaScript ファイル

// xyz.js
function do_xyz() {
  'use strict';
  alert("do_xyz");
}
window.onload = function() {
  'use strict';
  U.addEvent(U.$('w3c_dom'), 'click', do_xyz);
}

HTML ファイル

<!-- HTML ファイル -->
<script src="utilities.js">		
<script src="xyz.js">		
<input type="button" id="w3c_dom" value="実行">

下記はユーティリティによるボタンクリックイベントを実装した例である。

なお、これは onclick や onload だけでなく、onchange など、他のインラインのイベントハンドラでも同様である。

この書き直しが単純ですむ場合もあれば、引数によって複数のボタンの

たとえば、言語のページの指定によって切り替えるには、 JavaScript 読み取り HTML lang 属性 (www.delftstack.com) によれば、document.documentElement.lang によって属性のインスタンスを取得することができるので、これを使う。 たとえば、WEB ページで、<html lang="ja"> という指定があれば、document.documentElement.lang には ja という値が入る。

document.write() 、document.writeln() の削除

ドキュメントを動的に生成するときに使えるのが、これらのメソッドである。 しかし、これらを濫用することは DOM 表現を乱す。そのため、これらのメソッドの使用は極力控えるべきだ。 代わりの方法として考えられるのが、innerHTML を使う方法である。例えば、書き換え前は次の通りだったとする。

HTML ファイル

...
<script>document.writeln('<p>HTML 文</p>')</script>
...

これを書き直すと次のようになる。HTML ファイルに id をもつブロックが挿入されていることと、 innerHTML を window.onload 内で呼び出すことに注意する。

JavaScript ファイル

// write.js
window.onload = (event) => {
    'use strict';
    let s = '<p>HTML 文</p>';
    document.getElementById('sentence').innerHTML = s;
};

HTML ファイル

<script src="write.js"></script>
...
<div id="sentence"></div>
...

window.onload 内で呼び出さないと、読みだすべき id を含む DOM がすべて読み取れていることの保証ができない。

なお、window.onload も考えてみれば複数のイベントが登録できないわけだから、
window.addEventListener("load", function, false)
のような形で呼び出すべきなのだろう。

また、innerHTML のほか、insertAdjacentHTML() メソッドを使うことも考慮すべきだろう。

オブジェクト化の徹底

他にもある。大きな反省点は、まったくオブジェクト化がなされていないということだ。C++でいうクラスを作る作業は、 C++よりずっと簡単にできる。それなのに、必要な分析と実装をさぼっているのが現実である。

オブジェクトのキーと値との反転

オブジェクトのキーと値との反転には、いろいろな方法があることがわかった。 オブジェクトのキーと値の入れ替え(iifx.def) を見ると、 いろいろ書いてある。次はその一例である。

const originalObject = {
key1: "value1",
key2: "value2"
};

const swappedObject = Object.fromEntries(
Object.entries(originalObject).map(([key, value]) => [value, key])
);

console.log(swappedObject); // Output: { value1: "key1", value2: "key2" }

2次元配列の初期化

r 行 c 列の配列を宣言し、同時に 0 で初期化するには次のように書く。


	let twoDimensionalArray = Array.from({ length: r }, () => Array(c).fill(0));

次のようにも書ける


	let twoDimensionalArray = Array.from(Array(rows), () => Array(c).fill(0));

Array.from を使わないと、次のような少し回りくどい方法になる。


	let twoDimensionalArray = Array(r).fill().map(() => Array(c).fill(0));

参考

表の操作

表の操作には insertCell 、insertRow などのメソッドがある。Node オブジェクトの項を参照。

連番の生成

配列に連番を生成するスマートな方法がある。Array.from()(developer.mozilla.org)

// 連番の生成
// 配列はそれぞれの場所が `undefined` で初期化されるため、
// 以下の `v` の値は `undefined` になる
Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]

配列の最後の要素

配列の最後の要素を求めるには Array.prototype.at() を用いる。以下は、 Array.at()(developer.mozilla.org) からの例である。array1[-1] とはできないことに注意。


const array1 = [5, 12, 8, 130, 44];

let index = 2;

console.log(`An index of ${index} returns ${array1.at(index)}`);
// Expected output: "An index of 2 returns 8"

index = -2;

console.log(`An index of ${index} returns ${array1.at(index)}`);
// Expected output: "An index of -2 returns 130"

重複要素の数え上げとヒストグラム

const arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] の数字の重複数を求めたい。重複を省いた集合や配列を得るだけであれば Set を使えば可能であるが、 重複している数、即ち度数分布(ヒストグラム)も求めたいのであれば次のように書ける。

const arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
let count = {};

arr.forEach(e => {
  count[e] = (count[e] ?? 0) + 1;
});

このコードでは Null 合体演算子 (??) を使って、count[e] が定義されないときに限り 0 で初期化している。 論理 OR 演算子(||)を使ってもこの場合の結果は同じだが、Null 合体演算子を使うのがよいと思う。 また、細かいことだが、forEach の仮引数を i ではなくて e としたのは、forEach で得られるのが要素(element)であってインデックス(index)ではないことを明確にしたかったからである。

組合せ

`n` 個の配列 `[1, 2, 3, cdots, n]` から `k` 個選ぶ `{::}_nC_k` 通りの組合せをすべて生成する方法が、 JavaScriptで関数型っぽく順列組み合わせを書いてみた(qiita.com) で掲載されている。

1次元配列から2次元配列へ

1 次元配列 `a = [a(1,1), a(1,2), cdots, a(1,n_1), a(2, 1), a(2, 2), cdots, a(2, n_2), cdots, a(k, 1), a(k, 2), cdots, a(k, n_k)]` がある。 この配列と `n = [n_1, n_2, cdots, n_(k-1)]` を与えて、2 次元配列 `A = [[a(1,1), a(1,2), cdots, a(1,n_1)], [a(2, 1), a(2, 2), cdots, a(2, n_2)], cdots, [a(k, 1), a(k, 2), cdots, a(k, n_k)]]` を作りたい。

例 `a = [1,2,3,4,5,6,7]` に対して `n = [2, 3]` を与えて、`A = [[1,2],[3,4,5],[6,7]]` を作る。つまり、`A`.flat() = `a` であると同時に、 `n` = [a[0].length, a[1].length] である。


	const a = [1,2,3,4,5,6,7];
	const A = [2,3].reduce((acc, val) => [...acc.slice(0, -1), acc.at(-1).slice(0, val), acc.at(-1).slice(val)], [a])
	console.table(A) // [[1,2],[3,4,5],[6,7]]

参考

まりんきょ学問所JavaScript 手習い > 反省点


MARUYAMA Satosi