◆ クラス風に this を使って状態を持つか
◆ 関数の静的スコープで状態を持つか

関数が参照できる変数

ある関数が内部で作った値以外の特定の値を参照するには

  • 引数として渡されてる
  • 関数作成時の静的スコープ (グローバル変数見れるのも一応これ)
  • this コンテキストを設定して呼び出し

のどれかになるかと思います

1 番目の引数は関数の基本で 通常は引数で受け取ったものを処理するのが関数のはずです
でも設定とか状態とか引数以外のデータも参照する必要があるケースも多いです

そのときに設定類も毎回引数で受け取るか 2 番目や 3 番目の方法で参照するか今回はそのあたりの話です

静的スコープ

関数の中では関数が作られた場所でその場所で参照可能な変数にもアクセスができます
その関数を全然違う場所で呼び出しても 作成時に参照可能だった変数にはアクセス可能です

この方法は名前通りに静的なもので関数を作った時点で決まってしまってます
グローバル変数も作成時に見えるスコープなので この方法で参照できる変数の一つです
その関数中の処理や 同じスコープを参照可能な関数から参照可能な変数の値を変えることはできますが 後述の this のように実行時にこの変数を見るようにと柔軟な書き換えは難しいです

実行直前に値を置き換えておくことはできますが そういうことをすると再帰的に しかもそれぞれ参照すべき値が異なる可能性がある条件で呼び出すことになったら大変です
毎回実行前に今のデータを退避して変数を置き換えて終わったら戻すとかはさすがにつらすぎます
呼び出す側が呼び出す前にこういったことをしないといけないのは辛いので関数の中で最初と最後にこれらの処理をする というのが考えられます
そうしてしまえば呼び出す側は見るべき値を渡すだけでよく 使う側の手間はなくなりますが それだと関数の引数として見るべき値を渡すことになり そうするなら変数置き換えなんかせずに直接受け取った値を使えば良くて 1 番目の方法と大差ありません

this

JavaScript は言語の仕組みとして関数をオブジェクトのプロパティというコンテキストで呼び出すと そのオブジェクトを this で参照できます
また関数のメソッドである call や apply を使って this を自分で設定することもできます
これをうまく使ってるのがオブジェクト指向的な考えの使い方です

毎回同じ関数を作らない

静的スコープを使うと 上に書いたように関数作成時に参照できる値が決まります
その作成時に決まった変数を置き換えながら使うのは無理があるので 通常は作成時のスコープで参照できる値のみ参照します
そうなると 参照する値ごとに毎回同じ関数を作ることになります
同じ関数であっても参照している変数だけは別物です

this を利用すると メソッドとして呼び出した元のオブジェクトを自動で参照できるので一つ関数があれば同じプロトタイプからなるオブジェクト全てに使えます
そのオブジェクト以外でも call/apply を使えば他のものにも使えます
単純な関数だと 毎回関数も含めてオブジェクトを作って返すだけでもいいのですが this とプロトタイプチェーンのおかげで関数をプロトタイプのひとつだけに用意してオブジェクトごとに関数を作らずに済ませられます

function A1(value){
return {
value,
plus1(){ this.value++ }
}
}
const a = A1(1)
console.log(a.value)
// 1
a.plus1()
console.log(a.value)
// 2

これだと A1 を呼び出すたびオブジェクトが作られて 作られるものには plus1 関数も含まれます

function A2(value){
this.value = value
}
A2.prototype.plus1 = function(){ this.value++ }
const a = new A2(1)
console.log(a.value)
// 1
a.plus1()
console.log(a.value)
// 2

こうすれば plus1 は A2 のインスタンスがいくつあっても 1 つだけです
new A2 では value だけがプロパティのオブジェクトを作っていて __proto__ に A2.prototype が設定されています
a.plus1 を参照しようとすると自身のオブジェクトにないのでプロトタイプチェインをたどった次のオブジェクト A2.prototype を探して plus1 関数を見つけることができます

プロトタイプチェーンと this を使えば毎回関数を作らずとも良いので無駄が少なくメモリも節約されます
これが JavaScript のスタンダードな使い方です

引数渡しを兼ねる

この prototype に関数を作る方法ですが 最初にあげた 1 番目の引数渡しの方法も兼ねています
JavaScript では関数の call メソッド (apply メソッド) を使って this を設定できるので

class W {
constructor(){
this.values = []
}

add(x){
this.values.push(x)
}

getTotal(){
return this.values.reduce((a, e) => a + ~~e, 0)
}
}

// 普通に使う
const w = new W()
w.add(10)
w.add(20)
w.add(30)
console.log(w.getTotal())
// 60

// 関数として使う
W.prototype.getTotal.call({ value: [1, 2, 3] })
// 6

ということができます
〇〇.prototype.◯◯.call と呼び出しがちょっと長いですが this を任意のオブジェクトにすることもできます

そんな感じで万能だしこれでいいかと思っていました

関数いっぱい作ってもいいんじゃないかな

静的スコープじゃなく this を使う主な理由は同じ関数をいっぱい作るのがリソースの無駄ということでした
ただ 最近では const が使えるようになってイミュータブルが当たり前 毎回新しく変数作ればいい って考えが普通になりつつあります
メモリなんていっぱいあるし JavaScript の処理速度も十分に速いです
ブラウザ上で動く程度のものなら 変な最適化とかで扱いづらいコードにする必要もなく 関数がインスタンスごとに作られる程度大した問題じゃないです
実際 this を使ったオブジェクト指向風じゃなく静的スコープで状態を保存してるライブラリもたまに見ます

メリット兼デメリットですが 静的スコープにするとオブジェクトみたいにオープンなプロパティとしてアクセスできないので private プロパティのように扱えます
よくあるクロージャの例のあれです
意図せず外部から操作されることはないものの デバッグが面倒ですし this でオープンなプロパティとしていれば使う側がいろいろな都合で最終手段としてプロパティ書き換えて対処したりということができたりもしますが private になって外から参照できなくなるとそれもできなくなります
WebComponents の ShadowRoot でも Google の中の人が closed はあまり推奨しないとか書いてましたし 基本オープンにしておいて やろうとすれば無理やりなことで解決できるというのはメリットでもあるのでそれをなくすのはデメリットでもあります

文だけじゃ分かりづらいのでコードにしてみます
まず普通のクラス構文で this を使うものです

class X {
constructor(value){
this.value = value
}

add(num){
this.value += num
}

get(){
return this.value
}
}

const x = new X(100)
x.add(10)
console.log(x.get())
// 110

静的スコープにするとこう書けます

const Y = value => ({
add(num){
value += num
},
get(){
return value
},
})

const y = Y(100)
y.add(10)
console.log(y.get())
// 110

コンストラクタの処理を含むなら

const Y = value => {
const data = {
value: value + 100,
}

return {
add(num){
data.value += num
},
get(){
return data.value
},
}
}

const y = Y(100)
y.add(10)
console.log(y.get())
// 210

こういう形になります

複数のメソッドを返すためオブジェクトを返していますが 1 つしか行うことがなく設定のみ指定するなら

const f = settings => input => convert(input, settings)

こんなシンプルな形にもできます

this は便利な半面 めんどうなこともありますし いっその事 this は使わないという宗派の人もいるくらいです
こういう書き方もありなのかもしれませんね