「コールバック」も「アロー関数」も「無名関数」も全部同じ関数でしかないという話
コールバックって何?アロー関数って何?無名関数って何?と思ってここに来たと思ます。
まず、所誠は全部関数です。
出来る事は全く同じで単に呼び方とか呼び出し方が違うだけ。
つまり、引数を渡してその結果を受け取る。
ぜーんぶ同じです。どれもfunctionです。
なので、じゃあ何が違うのかという話を今回は説明するだけ。
大前提としてまず普通のfunctionをどうやって使うのかを思いだす所から。
1.普通のfunction
function sample(n) {
console.log(n);
}
sample(100);
だいたいこんな感じですよね。
『function 関数名(引数){処理}』
はい。合っています。
つまり、関数は特定の処理を何度も使うので独立させておいて、関数名を呼ぶことでその処理を引数を変えて何度も実行させます。
そういった場合に実は関数って呼んでるその機能って名前無くても成立しますよね?
関数の機能の部分って、『(引数){処理}』ここだけじゃないですか?
『()「引数を渡す部分」』と『{処理}「実際に引数を使って処理する部分」』
関数を関数たらしめる機能ってこれしかない
つまり、関数って実は『(){}』必要なのってこれだけだよねって言うのが全貌です。
関数=『(){}』このたった二つの括弧が関数って分かればもう「コールバック」も「アロー関数」も「無名関数」も9割理解出来たと思ってよいです。
1-2.関数は変数に代入出来る
もう一つ理解しておくべきことはこれです。
関数は変数にも代入出来る。
これは処理結果という話ではなくて、関数そのものを代入出来ます。
function abc(aaa){return aaa+1;}
という処理を作ります。
次にこの関数実行する場合
const a=abc(9);
console.log(a);//10
こうですね。『変数=関数()』
括弧を付けるとその関数が実行出来て、その結果が代入出来る。
次に関数を代入したい時は括弧を付けないで代入します。
const bbb = abc;
const b = bbb(9);
console.log(b);//10
関数本体を渡したいので引数などはいりません。
1つ手前で言った通り、括弧を付けると関数が実行されてしまう為です。
実際にはメモリの参照位置を渡してるんですが関数がそのまま代入されるって理解で一先ずOKです。
一旦理解したいのは関数は変数に代入出来るという事です。
ここまで理解できれば、無名関数もアロー関数も99%理解したと思ってよいです。
2.無名関数・アロー関数
無名関数とかアロー関数って他のサイトでも色々なサイトが説明してるけど分かり難い。
なぜなら、無名関数とかアロー関数の説明をしてるサイトって何処も
『const ccc = function(n) { console.log(n); };』
って、説明してるじゃないですか?
無名とか言ってるのに名前つけてるじゃん????
『const ccc = function(n) { console.log(n); };』
ってするんだったら、そもそも
『function ccc(n) { console.log(n); };』
でいいよね????
なんか違うの????
さすが!天才!そこまで理解できれ完璧です。
そう、全く違いは無いです。
じつは、関数として処理内容が同じならどっちで書いても全く同じ処理です。
なので、基本的には
// 普通の関数
function ccc(n) { console.log(n); }
// 無名関数
const ccc = function(n) { console.log(n); };
// アロー関数
const ccc = (n) => { console.log(n); };
最初に説明したとおり、この三つ全部が関数の機能としては完全に全く同じです。
そうなんですよ。
名前を付けてしまえばどれも同じです。
この3つのパターンどれで定義しても実行方法も結果も同じです
じゃあなんで呼び方が違うのか????
はい。今回の話はそこです。
まず、名称に関してですが1つ前のセクションで説明した通り、関数は変数に代入出来ます。
前のセクションでは普通に定義した関数を別の名前で定義しなおしました
function abc(){処理}
const bbb=abc();
ですが無名関数はそもそも、名前付きの関数を定義していません。
const ccc=function(){処理}
はい。みたまんまです。「なまえを付けてない関数」を代入しています。
説明上必要だから一旦代入してるだけで無名関数っていうのがさしている部分は
『function(){処理}』
この部分です。
ここだけ見れば確かに関数名…つまり名前はない無名関数という名称には納得がいく。
でも・・・確かに名前は付いてないけど・・・それって名前がないと呼べなくね?
そう、その通り。
ついさっき説明した通り、何かに代入したらほぼほぼ普通の関数とかわりませんし、そうなったら利点も薄い。(巻き上げは後述)
つまり、名前無しで呼べるタイミングがあるって事です。
それがコールバックです!
3.「コールバック」も同じ関数である
さて、話の続きです。
関数という部分「(){}」は必要だけど、名前が無くても別にいいタイミングある。ってマ?って話でしたね。
実は、この名前を使わないで関数部分だけを処理する・・・これが実はコールバックなんですよ(大事なので二回目)。
簡単なものからいくと例えば、『forEach』とかですね。
array.forEach(コールバック文);
名前を書かないつまり、この「コールバック」ってかっこよく言ってるけどこれって実はただの関数の事なんですよ。
最初にも言ってる通り、「コールバック」も「アロー関数」も「無名関数」も全部同じ関数です。
関数という機能は同じです。つまり・・・
array.forEach(関数);
実際にはこれだけです。
初学者はコールバックが良く分からなくて詰まってしまう人が割と多いのですがただの関数と違いは無いんですよ。
なーのーで、必ずしもアロー関数を書く必要は全然ありません。
例えば、forEachなどは何処のサイトに行っても間違いなく
array.forEach((value, index) => {
// 処理内容
});
って書いてあって、初学者はアロー関数の記述になじみが無いから一瞬でパンクして頭が「???」になるわけです。
ですが、先ほど言った通り、コールバックっていうのはただの関数なので普通の関数でも良いんですよ。
つまり、一旦このコールバックって言うところに普通に関数を記述してみれば初学者も理解しやすい・・・と思うのにどのサイトもアロー構文しかないんですよね。
「コールバックってアロー構文じゃなくてもかけるの?????」
当然問題ありません。
無名関数とかアロー関数っていうのは元々の関数を記述際に別に名前いらないよね?っていう関数の進化でしかないので別に関数を普通に記述しても問題ないです。
という訳で、forEach文を普通に名前付き関数で書くと・・・
// 2. forEachの引数の中で、そのまま「function 名前()」を定義する
const ar = [1, 2, 3];
ar.forEach(
function eee(v) {
console.log(v);
}
);
見ての通りこの様に記述出来ます。一般的な普通の関数です。
『function eee(v) {console.log(v);}』
関数eee(引数){処理}これだけ。
今回最初に書いた普通の関数と同じです。引数を取得してそれをそのまま表示するだけ。
この時に、第一引数として取得出来るのが配列の中身です。
なーのーでこの「v」には配列が順番に渡されるので1・2・3と表示されます。
たったそれだけです。
mapとかfilterとか他にもコールバックっが使えるメソッドがありますが。
引数にその中身が連続で渡されるだけです。
これでまた、話が最初に戻ります。
「コールバック」も「アロー関数」も「無名関数」も全部同じ関数
なーのーでー・・・次に無名関数にしてみましょう。
だって、ここで必要なのって引数を受け取る『 () 』この括弧と処理を入力する『 {} 』ここしか必要ないじゃないですか?
// 1. 普通の関数を無名関数にする=関数名が無くなるだけ
const ar = [1, 2, 3];
ar.forEach(
function(v) {
console.log(v);
}
);
勿論これでも動きます。
関数名があろうがなかろうが処理に必要なのは『(){}』これだけですもんね。
名前が無くなった所で処理に必要なものは足りています。
そしてここで更に「function」って記述いらないよね・・・?ってなって出来たのがアロー関数です。
アローで必要なのは『 () 』と『 {} 』それとそれをつなぐアロー『 => 』だけです。
// 3. 無名関数をアロー関数にする
const ar = [1, 2, 3];
ar.forEach(
(v) => {
console.log(v);
}
);
はい。全く同じ処理がこれだけ短くなりました。
殆ど関数としての原型が無い・・・。
そう、初学者はこれだけで良く分からなくなっちゃう。
なので関数って実質『(){}』たったこれだけなんだぁって理解出来ればOK!
ここまできたらもう一押し理解しましょう。
javascriptを作ってきた人たちはもっと概念的理解にたどり着きます
関数が「(){}」だけでいいなら・・・『引数』『処理結果』だけあればそもそも『 () 』も『 {} 』も要ら居ないんじゃない?
つまり・・・
// 3. 更に省略したアロー関数
const ar = [1, 2, 3];
ar.forEach(v => console.log(v));
使用頻度が高い引数が1つで処理が1つ(returnが1つ)だけならこれでいいよね?
っていうのがjavascriptのアロー関数って事です。
初学者がこれを関数だと理解出来ないのはもうしょうがないと思います。
もう概念なんですよ。『引数と結果』っていうこういう処理概念です。
とは言え、コールバックっていうのが関数だという所だけ分かっていれば理解も出来るし、慣れます。
そして、慣れると無駄な文字を書かなくて良いのでやはり利便性が勝ります。
最後に、でも複雑な処理がコールバック部分に入ったら見辛いのでは・・・?
はい。見辛いです。
ですが、コールバック部分に入れるのが関数つまりは関数を代入すれば自動でそこに引数を渡してくれるんです。
つまり
// コールバック部分に関数を直接代入
function eee(v){ console.log(v) };
const arr = [1, 2, 3];
arr.forEach(eee);
このように記述する事も出来ます。
特に多いパターンはDOMのイベントリスナーとかは処理が大きくなったらこの様に関数を分離してコールバックで関数を呼ぶというパターンも増えます。
ちなみに、このイベントリスナーとかで関数に間違えて括弧を付けてしまい関数が実行されるっていうのが良くある話なので「何を代入したいか」というのを忘れずに。
4.唯一の大きな違い「巻き上げ」と「再代入」
「機能は全部同じ」と言いましたが、JavaScriptの挙動として一つだけ決定的な違いがあります。
それは、「いつ呼び出せるか」と「上書きできるか」というルールです。
① 巻き上げ(Hoisting)
普通の function で宣言した関数は、コードのどこに書いても「一番上にあるもの」として扱われます。
つまり、定義する前に行をまたいで呼び出すことができます。
test(); // エラーにならない!実行できる
function test() {
console.log("動くよ");
}
対して、変数に代入する「無名関数」や「アロー関数」は、代入する前に呼ぶとエラーになります。
変数は宣言されるまで存在しない扱いだからです。
② 再代入の禁止(constによる保護)
普通の function は、後から同じ名前で関数を作ると上書きされてしまいます。
しかし、const を使ってアロー関数を定義すれば、間違えて同じ名前の関数を作ろうとした時にエラーで教えてくれます。
const myFunc = () => { console.log("絶対守るマン") };
// 同じ名前で作り直そうとするとエラーが出る(安全!)
const myFunc = () => { ... };
③暗黙return
軽く触れたけれどアロー関数はとにかく短く記述する事が目的に出来ている。
なので『引数』『結果』が最短記述出来る様に出来ておりreturnが1つだけの場合、暗黙的に結果をreturnしてくれる
『v=>v+1』とした場合、これの実行結果はvが引数として処理されv+1が自動的にreturnで返って来る。
これら「暗黙return」と「 {} 不要」の必要条件は、式が1つである事。
もっと簡単い言えば、ブロックが1つである事。更に言えば「 ; 」が無い事です。
そのため式が一つであった場合でも「 ; 」が付くと暗黙returnと括弧なしが利用出来ません。
最短記述をしたい場合は「 ; 」は消す必要があります。
また、引数側は引数が一つの時のみ「 () 」が省略できます。
引数が複数ある場合『 (a,b)=>a+b 』みたいにする必要があります。
④アロー関数はthisを拘束しない(Lexical this)
アロー関数の最大の武器は、「自分自身の this を持たない」という性質です。
JavaScriptの this とは、簡単に言えば「その処理を動かしている実行主(オーナー)」を指します。しかし、従来の function で書かれた関数(無名関数含む)は、呼び出し方によってこの this がコロコロと変わってしまう「浮気性」な性質がありました。
アロー関数はこの問題を解決し、「関数が定義された場所の this を一生使い続ける」という固定の挙動をします。
【無名関数として使う場合の違い】
イベントリスナーや setTimeout などで「使い捨ての関数(無名関数)」を渡す際、その差が顕著に出ます。
・従来の無名関数: 実行時に this が「ボタン要素」や「Window」に勝手に書き換わります。
・アロー関数の無名関数: 外側のクラスやオブジェクトの this をそのまま保持します。
これにより、const self = this; といった「thisの退避」という古いテクニックが不要になりました。
【メリットとデメリット】
| メリット | デメリット(注意点) |
|---|---|
this の混乱がなくなり、コードが直感的になる。 |
オブジェクトのメソッドとして使うと、そのオブジェクト自身を this で参照できない。 |
| 記述が短くなり、コールバック関数がスッキリ書ける。 | arguments(引数の集合)や new(コンストラクタ)が使えない。 |
「その場限りの処理(コールバック)」や「外側の this を使いたい時」はアロー関数。「オブジェクトのメソッド」や「this を動的に切り替えたい時」は function、という使い分けが現代のスタンダードです。
まとめると、「どこでも呼べる自由な function」か、「ルールが厳格で安全な const + アロー関数」か、という使い分けになります。
最近の開発でアロー関数が好まれるのは、この「勝手に書き換わらない安全さ」があるからでもあります。