JavaScriptで学ぶコールバック関数と高階関数の使い方と重要性
今回は、JavaScriptにおけるコールバック関数と高階関数について学習していきます。
コールバック関数は、JavaScriptで最も一般的に使用される関数型プログラミングの手法の一つです。
初心者の方でも、知らず知らずのうちに使用していることがあるかもしれません。
JavaScriptには、高階関数が組み込まれており、これらを使うことで、より柔軟で効率的なコーディングが可能となります。
例えば、map()
、reduce()
、filter()
メソッドなどが挙げられますが、これらが高階関数であることに気付かずに使用している方もいるかもしれません。
もしかしたら、すでに高階関数を書いたり使ったりしている方もいるかもしれませんが、その重要性に気付いていないこともあるでしょう。
この記事では、JavaScriptのコールバック・高階関数の概念を理解し、それらを使いこなすための基礎知識を身につけることができます。
関数については下記で解説しております。
コールバックと高階関数は、JavaScriptで最も誤解されている概念の一部であり、複雑に思えるかもしれませんが、そんな事はありません。
まずは何かを学び始める前に、それらがなぜ重要なのかを理解する必要があります。
重要性
高階関数を使用すると、シンプルでクリーンなコードを記述できます。
この構成は、単純で読みやすいコードになりテストとデバッグが簡単となり、バグが少なくなります。
コードを簡潔にしたい場合、コードをより柔軟で保守しやすくするために特に役立ちます。
コールバック関数
コールバック関数はJavaScriptの重要な部分の1つです。
これは主に、同期タスクまたは非同期タスクの完了に応答して関数を実行するために使用されます
ほとんどが非同期動作で非常に役立ちます。
例えば、ユーザーがボタンをクリックするタイミングは誰にも分かりません。
なので、ボタンのクリックイベントにコールバック関数を渡し、ユーザーがボタンクリックするたびにコールバック関数が実行され、それに応答されます。
Webサイトをインタラクティブに設計し、反応できるようになります。
高階関数
JavaScriptにおいて、関数はオブジェクトの一種であり、高階関数とは、関数を引数として受け取ったり、関数を返したりして、他の関数を操作する関数のことを指します。
高階関数は、1つ以上の関数を引数として受け取り、または関数の結果を返す関数です。
たとえば、下記のような関数があります。
function higherOrderFunc(greeting) { greeting(); } higherOrderFunc(function() { console.log('Hello'); }); higherOrderFunc(() => console.log('Hello'));
上記では、関数higherOrderFuncは引数として関数を受け取り、その関数を実行しています。
また、別の関数を返すこともできます。
以下の例では、greeting関数が別の関数を返しています。
function greeting() { return function() { console.log('Hello!'); }; } greeting()(); //二重括弧で関数を実行
この関数は、別の関数を返すことによって、高階関数となります。
上記ではreturnで他の関数を返し、それを受け取っています。
引数の受け入れか、関数の戻りの両方を実行することは、必ずしも必須ではないことに注意してください。
いずれかを実行した際、その関数は高階関数になります。
高階関数には、一般的にmap()
、reduce()
、filter()
などのメソッドがあります。
たとえば、下記のように数字の配列を作成し、map()
メソッドを使用して新しい配列を作成することができます。
function myArray() { const arr1 = [1, 2, 3, 4] const arr2 = arr1.map(function(num) { return num * 2 + ' ' }) return arr2 } console.log(myArray()) // 2, 4, 6, 8
上記の例では、map()
メソッドは、引数として関数を受け取っており、各要素を2倍にした新しい配列を作成しそれを返しています。
map
は引数として、他の関数を受け入れています。
また、下記の例では、単純な足し算を行い、map()
メソッドを使用して新しい配列を作成しています。
const arr2 = arr1.map(function(num) { return num * 2 + ' ' })
他の関数で操作をしています、この時点でこれは高階関数となります。
初学者にとっては、map()
メソッドは便利なのでよく使われますが、実際には、map()
メソッドは高階関数であることを理解しておく必要があります。
高階関数とは、単に関数を受け取るか、または返す関数にすぎません。
それだけであり、これ以上深く考える必要はありません。
以上が、JavaScriptにおける高階関数についての基本的な説明となります。
コールバック関数
コールバック関数とは、外部関数内で呼び出される引数として別の関数に渡される関数です。
これは、高階関数の引数として渡され、後で実行される可能性があります。
また、コールバック関数は値、関数、または何も返さない可能性があるため、高階関数から呼び出された後に行う必要がある作業がある場合に便利です。
コールバック関数を作成するには、無名関数、アロー関数、または引数として渡す方法があります。
名前付き関数をコールバック関数として使用することもできます。
さらに、同期コールバック関数と非同期コールバック関数もあります。
それでは、例を見てみましょう。
function callback() { return "Hello, "; } function greeting(Message, name) { console.log(Message() + name); } greeting(callback, "Tarou"); //実行時の引数に関数と値を渡す // 出力: Hello, Tarou
上記の例では、コールバック関数が関数callback()
です。
この関数は、"Hello, "を返します。
次に、greeting()
という高階関数があり、Message
とname
の2つの引数を取ります。
ここで、Message
はコールバック関数であり、関数callback()
が渡されます。
greeting()
が呼び出されると、Message() + name
がログに出力されます。
ここで、Message()
はコールバック関数callback()
を呼び出しその結果を返します。
下記も別の例となります。
// 通常の関数 function myFunction() { console.log("コールバックが呼ばれた"); } //高階関数 function otherFunction(callbackFn) { callbackFn(); } otherFunction(myFunction); //関数を渡す //出力 コールバックが呼ばれた
上記の例では、myFunction()
という名前付き関数をコールバック関数として使用しています。
関数otherFunction()
は、コールバック関数を引数に受け取り、その関数を実行します。
最後に、関数myFunction()
が呼び出され、"コールバックが呼ばれた"というメッセージがログに出力されます。
さらに別の例でも見てみましょう。
下記のようなgreet
関数があります。
この関数は、引数として渡された名前を使ってあいさつのメッセージを表示し、さらに別の関数をコールバックとして実行します。
// 高階関数 function greet(name, callback) { console.log('Hi' + ' ' + name); callback(); }
ここで渡される2つの引数は、文字列の値と関数です。
callMeという関数は、コールバック関数として使われます。つまり、別の関数内で引数として渡される関数は、コールバック関数となります。
下記のコードでgreet
関数を呼び出すと、'Hi Taro'
というメッセージが表示され、次にcallMe
関数が実行されます。
function callMe() { console.log('I am callback function'); } greet('Taro', callMe);
上記でしたら文字列の値と関数になります。
「callMe」関数はコールバック関数となります。
このように、高階関数を使用することで、コードの再利用性や柔軟性を向上させることができます。
コールバック関数を使って、関数が完了した後に実行される追加の処理を定義することができます。
つまり、コールバック関数は、高階関数に渡すための関数であり、高階関数内で呼び出されます。
コールバック関数を作成するには、様々な方法がありますが、必要に応じて適切な方法を選択することが重要です。
コールバックとして無名関数を使用する
コールバック関数は、無名関数としても定義できます。
通常、コールバック関数は、Array.prototype.map()などの関数に渡して非同期処理を行うために使用されます。
以下は、mapを使用しない場合の無名関数での定義例です。
let greeting = function (name){ console.log("Hello" + " " + name) } greeting("Tarou") //Hello Tarou // アロー関数 const arrowFunc = (name) => `Hello${name}` console.log(arrowFunc("Tarou")) // Hello Tarou
アロー関数を使用すると、より簡潔な関数構文を記述できます。
例えば、下記のような場合に使用できます。
function setGreeting(name, x) { x(`Hello ${name}!`); } setGreeting('Tarou', (greeting) => { console.log(greeting); });
アロー関数は、名前付き関数として使用できないため、変数に割り当てる形で無名関数として使用します。
ただし、無名関数はデバッグが困難になる場合があるため、使用する際には注意が必要です。
コールバックは、DOMイベントやタイマーなど、様々な場面で使用されます。
たとえば、下記のようにaddEventListenerを使用することができます。
window.addEventListener("click", callback => { 何かしらの処理 })
DOMイベントだけでなく、一般的なのは例えばタイマーなどでも使用されます。
setTimeout ( () => { } 、3000); // 3秒後に実行
ではaddEventListenerにる、より一般的な例を試してみましょう。
下記は、DOMイベントリスナーでコールバック関数を使用する例です。
まず、HTMLファイルを作成します。
<body> <button id="myButton"></button> </body>
そして、下記のようにJavaScriptファイルを作成します。
let myButton = document.getElementById('myButton') myButton.addEventListener('click', response); function response() { console.log('クリックされました。') }
上記のコードでは、response
という関数がaddEventListenerに渡されています。
ボタンがクリックされた場合、このコールバック関数が呼び出され、'クリックされました。'
というメッセージがコンソールに表示されます。
ボタンがクリックされない場合、コールバック関数は呼び出されません。
addEventListenerに渡されたresponse
は、コールバック関数として使用されます。
ボタンがクリックされるたびに、このコールバック関数が呼び出され、ユーザーの要求に応じて応答を返すことができます。
このように、コールバック関数は、単に渡される関数定義です。
この概念を理解することは、Vanilla.JSのイベントハンドラからReactでのStateおよび状態管理や、すべてを理解する上で非常に重要なので、注意してください。
そして、コールバックには、同期コールバックと非同期コールバックの2種類があります。
同期コールバック
JavaScriptはデフォルトで同期言語であり、シングルスレッドであるため、コードは新しいスレッドを作成して並行して実行できません。このため、コードは通常、上から下に順番に実行され、同期的に動作します。
高階関数を使用する場合、コールバックはその高階関数の実行中に実行されます。つまり、高階関数はコールバックの実行が完了するまで完全に実行されないため、コールバックは高階関数を一時的にブロックします。これが同期コールバックです。
次のコードを考えてみましょう。
function func1(test) { console.log('呼び出し 1'); test(); } func1(function func2 () { console.log('完了'); }); console.log('呼び出し 2');
上記のコードでは、func1
はコールバックと同期して実行されます。
つまり、func1
が戻るまで実行はブロックされます。
しかし、コールバックが同時に実行されるようにすることもできます。
この場合、func2
は同期コールバックと呼ばれます。
つまり、func2
は高階関数と同時に実行されます。
JavaScriptが同期言語であるため、コードの順序に注意することが非常に重要です。
コールバックが完了するまで高階関数がブロックされるため、コードの実行が予期しない方法で停止する可能性があります。
このため、JavaScriptでは非同期処理を使用することで、コールバックの実行を遅延させずに処理を進めることができます。
非同期コールバック
非同期コールバック関数は、高階関数の実行後に呼び出されます。
JavaScriptでは、操作の完了を待つ必要がある場合は、非同期処理が使用されます。
つまり、待機中に完了するのを待たずにコードの次の行の実行を開始することができます。
一方、同期コールバック関数は、すぐに実行されます。これらの違いにより、非同期コールバック関数は実行が遅延されることになります。
setTimeout()
というJavaScriptの組み込み関数があります。この関数は、関数の呼び出しまたは指定された期間(ミリ秒単位)後に式を評価します。
たとえば、下記のコードを見てみましょう。
function setNumber() { console.log(1); setTimeout(function() { console.log(3); }, 3000); console.log(2); } setNumber(); // Output: // 1 // 2 // 3
上記のコードでは、コールバック関数はsetTimeout()に渡され、何かが起こった後に3秒後に呼び出されます。
しかし、コードの実行は順番に行われず、非同期処理が使われているため、2が3よりも先に出力されます。
もう一つの例を見てみましょう。
function greet() { alert(`Hello`); } function introduceMyself(name) { alert(`私の名前は${name}です。`); } setTimeout(greet, 2000); introduceMyself("太郎");
上記のコードでは、greet
関数が2秒後に実行されます。
その間に、introduceMyself()
関数がすぐに実行され、"私の名前は太郎です"
とアラートされます。
2秒後、greet()
関数が実行され、"Hello"
とアラートされます。
つまり、非同期処理がすべてのコールバック関数に使用されるわけではなく、同期的に使用することもできます。
しかし、JavaScriptにおいて、コールバック関数を使用する場合、非同期処理が必要な場合が多いため、非同期処理とコールバック関数がよく組み合わせて使用されます。
無名関数での非同期コールバック
JavaScriptでは、非同期処理によってユーザー体験を向上させることができます。
ES6アロー関数を使ってコールバック関数を作成することもできます。
アロー関数は無名関数です。
例えば、以下のコードでは、setNumbar()関数内でconsole.log(1)
が呼ばれた後に、300ミリ秒後にconsole.log(3)
が呼ばれ、最後にconsole.log(2)
が呼ばれます。
function setNumbar(){ console.log(1); setTimeout(() => { console.log(3); }, 300); console.log(2) } setNumbar();
また、setTimeout()関数を使用することで、一定時間が経過した後に処理を実行することもできます。
下記の例では、3秒後にメッセージを表示しています。
setTimeout(() => { console.log("このメッセージは3秒後に表示されます") }, 3000);
コールバック関数は、別の関数内で呼び出される関数であり、引数を取りません。
しかし、無名関数を使うことで、引数を取ることもできます。
下記の例では、Arr
配列の各要素に対してmyCallback()
関数を適用しています。
let Arr = [1,2,3,4]; function myCallback(x) { return x + 1; } console.log(Arr.map(function(x) { return myCallback(x + 2); })); // [2, 3, 4, 5]
非同期処理では、ネットワークの速度や画像のサイズによっては、リモートサーバーから画像をダウンロードするのに時間がかかる場合があります。
setTimeout()関数を使用して、画像のダウンロードが完了するまで待機する時間を操作することができます。
コールバックの注意点
JavaScriptにおいて、複数に深くネストされたコールバックは一般的に「コールバック地獄」と呼ばれ、コードの可読性が低下し、スコープの問題を引き起こす可能性があります。
そのため、非同期コールバックが管理不能になるまで深くネストされたコールバックを回避することを強くお勧めします。
下記は、コールバック地獄の例です。
const messageHell = []; function hell() { setTimeout(function() { messageHell.push('Hello'); setTimeout(function() { messageHell.push('world'); setTimeout(function() { messageHell.push('javascript'); setTimeout(function() { messageHell.push('Please do not give me callback hell.'); console.log(messageHell.toString()); }, 100); }, 100); }, 100); }, 100); } hell();
この例は、あくまでも例ですが、より複雑な処理を行う場合、コールバック地獄はより深刻な問題となります。
しかし、この問題を回避する方法があります。
1ステップずつ進めて、コールバック地獄を回避しましょう。
当ブログでは、この問題についてより詳しく学ぶことができます。
JavaScriptを含む他のプログラミング言語でも、この問題は一般的に発生しますが、回避方法を理解することで、より効果的なコードを書くことができます。
最後に
コールバック関数とは、あとで実行される引数として別の関数に渡される関数であり、無名関数やアロー関数を使用することができます。
一方、高階関数は、他の関数を引数として受け取る関数を指します。
コールバック関数は、同期的または非同期的に実行することができます。
これは、コールバック関数が、別の関数に渡され、その関数によって呼び出されるためです。
一方、高階関数は、他の関数を引数として受け取るため、関数の機能を拡張することができます。
高階関数は、コールバック関数を引数として受け取ることができます。
コールバック関数と高階関数の重要性は、関数型プログラミングの基礎となっています。
これらの概念を理解することで、プログラマーはより高度なプログラムを作成することができます。
例えば、非同期処理を実行する場合、コールバック関数を使用して、処理が完了した時点で呼び出される関数を指定できます。
また、高階関数を使用することで、関数の再利用性を高めることができます。
以上のように、コールバック関数と高階関数は、プログラマーにとって非常に重要な概念です。
これらを正しく理解し、適切に使用することで、より高度なプログラムを作成することができます。
本日は以上となります。
最後まで読んで頂きありがとうございます。
本記事が参考になった場合は、ブックマークして他の方にも共有していただけると幸いです。