番号は「何番目か」ではなく、先頭からどれだけ離れているかだからです。
先頭は先頭から0だけ離れています。だから0です。
最後の番号は length - 1 になります。長さと最後の番号がずれるのは、
「距離」と「個数」が別のものだからです。
0 始まりだと計算が揃う
いちばん分かりやすいのは、範囲を切り出すときです。
取れる個数は、いつでも 終わり - 始まり です。
2 - 0 で2個、5 - 2 で3個。例外がありません。
slice(start, end) の end を含めない書き方を半開区間と呼びます。
0 始まりと半開区間は組で働きます。
隣り合う範囲が、そのまま繋がる
const fruits = ['あ', 'い', 'う'];
console.log(fruits[1]);
console.log(fruits[fruits.length]);前の終わりが、次の始まりになっています。 重なりも隙間もありません。
分割の処理が i += size だけで書けるのは、これが理由です。
→ 配列を決まった数ずつに分ける
繰り返しの条件も揃う
i < length で、ちょうど length 回まわります。
<= にすると1回多く回り、最後は undefined になります。
連番を作るときも同じです。
個数を渡すと、そのまま番号が並びます。
1 始まりだと思うと、どこがずれるか
- 1番目のつもりで
[1]を読むと、2番目が返ります - 最後のつもりで
[length]を読むと、範囲の外なのでundefined
どちらもエラーになりません。 静かに1つずれます。
最後の要素は length - 1 です。at(-1) のほうが読みやすくなります。
見つからないときが -1 なのも同じ理由
0 は「先頭で見つかった」という正しい答えです。
だから「無い」には、番号としてありえない -1 を使います。
if (fruits.indexOf(x)) と書くと、先頭にあるときに 0 が偽と扱われます。
→ 文字列を探す・分ける・置き換える
よくある誤解
「0 始まりは分かりにくいだけの慣習」
計算が揃うという実利があります。 上で見たとおり、 個数・範囲・繰り返しの条件が、すべて足し算と引き算だけで書けます。
1 始まりにすると、範囲の指定でどこかに +1 か -1 が必要になります。
この論点は 1982 年の E. W. Dijkstra の覚書(EWD831)で整理されています。出典は下に置いてあります。
「人に見せるときも 0 から数える」
それは別の話です。 画面に出す番号は i + 1 にしてください。
内部の番号と、人に見せる番号は分けます。
自分で確かめる
上のコードは書き換えて実行できます。
forの条件をi <= fruits.lengthにして、何が出るか見るslice(1, 1)の結果と長さを見る(1 - 1で0個)fruits[-1]とfruits.at(-1)を並べる
まとめ
- 番号は「順番」ではなく先頭からの距離
- 取れる個数は、いつでも
終わり - 始まり。例外が無い - 隣の範囲がそのまま繋がる(半開区間)
- 最後は
length - 1。読みやすさならat(-1) -1が「無い」なのは、0が正しい答えだから- 人に見せる番号は
i + 1。 内部の番号と分ける