関数の配置場所とスコープ
コードを読むときにスコープが広いと頭の中で読み取るときに大変ですよね
なので極力スコープは小さくしたいのですが 関数内だけ使う関数ってどこにあるのが良いのでしょうか?

const fn1 = () => {
const fn2 = () => {

}

fn2()
}

const fn3 = () => {

}

こんな感じのもので fn2 は fn1 の中でしか使いません
fn1 でしか使わないので fn1 の中 つまり今の場所でいいように思います

しかし fn1 のコードが長くなり fn1 の中に fn2 相当の関数が 5 や 10 になってくると fn1 関数がとても長くなり見づらくなります
関数内関数を無視して fn1 だけを見ると数十行程度なのに fn1 全体としては 数百行とかいうケースもありえます

また fn1 内のローカル変数がいくつもあると fn2 などの関数はそれらすべてを見ることができます
fn2 は外側を一切見ない関数だとしてもスコープ的には見れるので 読むときにはそれらを参照するかもしれないとして読む必要があります

そういうことを考えると

const fn1 = () => {
fn2()
}

const fn2 = () => {

}

const fn3 = () => {

}

でもいいのではと思うのですよね
fn2 は fn1 内のローカル変数を見ることができません
見れるのはグローバルやモジュール内の変数のみです
fn1 のローカル変数とは切り離されているので 読みやすくなります
また fn1 の中は fn1 で直接行う処理のみなので fn1 の行数も減ってスッキリします

しかし fn2 がモジュールのトップレベルに出てくることで fn3 が fn2 を参照できるようになってしまいます
今度は fn3 が fn2 を使うかもという部分を考えないといけません

いずれも全体として短いこれくらいだとどっちでもいいレベルですが 長くなってくると読みづらくなるんですよね
ただ fn3 が fn2 を見れても関数呼び出しだけです
それに対して fn2 が fn1 のローカル変数を見れるのは値の書き換えができるので複雑度が上がります
なのでどっちかというと関数内関数を減らしたほうがいいのかなと思ったりはするものの 本当にベストなのか疑問が残ります

別の手段としてモジュールを分けてしまうのがいいのかとも思ったりはしてますが モジュール数が結構増えそうで 結局試してません
分けるとしたらこういう感じです

// sub.js
export const fn1 = () => {
fn2()
}

const fn2 = () => {

}

// main.js
import { fn1 } from "./module1.js"

const fn3 = () => {

}

fn1 が読みづらいくらい長くなるなら fn1 だけを別モジュールに切り出して fn1 と fn2 をトップレベルに置きます
エクスポートは fn1 だけにして fn3 からは fn2 を参照できなくします

ただこれも トップレベルじゃないとできないです
親スコープを参照したいから関数内関数にしてるところがあって その中で今回みたいなことをしたい場合にはモジュールに分けるということはできないです
Chrome 118 で @scope 機能が追加される予定
https://chromestatus.com/feature/5100672734199808
https://drafts.csswg.org/css-cascade-6/#scope-atrule

CSS に @scope という機能が増えるようです

@scope (.foo) to (.bar) {
a { color: red; }
}

と書けば DOM のツリー上で .foo の内側で .bar の外側を対象に a タグの色を変えるということになります
ShadowDOM みたいなスタイルのスコープ機能が追加されるということですね
WebComponents を使うなら ShadowDOM でいいですが React 等のフレームワークの場合はコンポーネントごとに ShadowDOM を使わないのでスコープを制御し辛いです
なのでこういうのがあると便利になりそうです

これに合わせて Scope Proximity という新しい概念が追加されてスタイルの優先度にも変更があるようです
詳細度が同じ場合 セレクタの対象要素とスコープのルート要素間の距離が近いほど優先度が高くなります
スコープが指定されないこれまでのスタイル定義だと距離が無限(優先度最小)として扱われるようです

例えばタブがあって その中にフォームがあって タブとフォームにスコープが設定されていて フォームパーツは両方のスコープに入る場合 まずフォームスコープのスタイルが優先されて次にタブスコープのスタイルで 最後にページ全体のということのようです
優先度的には良さそうです
ただ詳細度の方が優先度が上なので やっぱり詳細度には悩まされそうです

構文的には

@scope [(<scope-start>)]? [to (<scope-end>)]? {
<rule-list>
}

なので scope-start や scope-end は省略できるみたいです
scope-end を省略すると

@scope (.dark-scheme) {
a { color: plum; }
}

CSS のネスト機能が使えるようになる前なら ネスト機能としても使えるので需要が高かったのかもしれません
でも今では先にネスト機能が使えるようになっているので あまりこのケースは使わなそうです

ネスト機能と同じようなことができますが 全く同じではなく少しだけ違いがあります
@scope に指定するセレクタ (上の場合の .dark-scheme) の部分は詳細度に含まれません
ネストの場合に詳細度に影響しないよう :where() を使うような挙動です
その場合でもスコープによる優先度があるので :where() を使うより @scope の方が優先度が高くなります

scope-start を省略する場合は その style タグの親要素がスコープのルートになります

<div>
<style>
@scope {
p { color: red; }
}
</style>
<p>ここは赤色</p>
</div>
<p>ここは赤色じゃない</p>

昔 style タグに scoped 属性がありましたが それに近いことができますね
Python は非ブロックスコープだけど hoisting がないので
if False:
a = 1
print(a)

これは None ではなく エラー
PHP でも同じ
JavaScript の var なら hoisting されてスコープの最初で宣言されることになるのでエラーにはならず undefined

これでエラーになると 結局ブロック直前で初期化の代入が必要
ブロックスコープじゃないのにブロックスコープの不便なところは真似しないといけない
Python の場合は var みたいな特殊な宣言文じゃなくてただの代入だから難しいんだろうけど
いっそブロックスコープにしてくれたらよかったのに
関数の実行結果と if 文を組み合わせるときにいつも思うやつ
const result = something(value)
if(result) {
something2(result)
} else {
something3(value)
}

こういうのやりたいことが多いけど result は次の if とそのブロックでしか使わないのにスコープが長くなる
どこで使われてるのか探すのが手間になるし const なので result って名前が以下で使えなくなる
かと言って 使いまわしたくもないから let で解決ってわけでもない
必要なのは次の if とそのブロック内だけなのに

ブロックスコープにすると解決はするけど
みやすさがいまいち

{
const result = something(value)
if(result) {
something2(result)
} else {
something3(value)
}
}

for みたいに if にも ブロック内だけで有効な変数宣言できたらいいのに

if(const result = something(value)) {
something2(result)
} else {
something3(value)
}

ほとんどはこれで解決する
けど ときどき代入結果の文字数が 3 文字以上だったり数値が 0 以上だったり そのまま if の条件に使えないときもある

if や for みたいにブロックスコープに意味を持たせれば見た目的にもわかりやすいし こういうことできたらいいのに

scope(const result = something(value)) {
if(result) {
something2(result)
} else {
something3(value)
}
}

これだと if のところに result を使った式を書ける

どうせなら const や let のブロックにできたらもうちょっと見やすいかも

const(result = something(value)) {
if(result) {
something2(result)
} else {
something3(value)
}
}

こうみるとけっこうありな気がする

const(x = foo(), y = bar()) {
//
}

let(x = foo(), y = bar()) {
//
}

でもこれって with でいいんだよね

with({x: foo(), y: bar()}) {
//
}

with は変数を使うと参照先がわかりづらいデメリットが大きいけどリテラルを使う限り with ってすごく良い仕組みだと思う
非推奨にするのもったいないなー