JavaScript: コールバック処理
概略
JavaScript ではコールバック処理を多用します。 すぐにいろんなところで出てくる使い方です。 例えば下のような書き方をします。
- 関数の引数で function(){...} が現れる。
func1( function(param){ //コールバック処理 });
- アロー関数で記述されたコールバック。
=> のような記号が出てきます。func1( param => コールバック処理 ); // => はアロー関数を表します。
関数式とコールバック
関数式
関数のような手続き処理も変数で扱うことができます。 関数も文字列や数値と同じように変数に代入することができます。 これを 関数式 と呼びます。
例: let str = "文字列"; //変数に文字列を代入 let value = 123; //変数に数値を代入 let callback = function(param){...}; //変数に関数を代入 (関数式) 参考: MDN:関数式
変数に関数を代入すると、下のような使い方ができます。
1) 変数名で関数を実行することができます。// 例: let callback = function(message){ alert(message); }; callback("こんにちは"); // 上の関数を実行し、alert のダイアログを表示する。
- TestJS_function02.html
関数を変数に代入、変数名で関数を実行など。
JavaScript ノート: 関数
例: let callback = function(param) { //コールバック処理 }; // 変数 callback を関数 foo の引数として渡す foo(callback);
参考で C言語では上の 1)、2) の話は関数ポインタへの代入、 関数ポインタで実行といった話で出てきます。 Java では関数ポインタの話がないので 不思議な使い方に見えるかもしれません。
無名関数でコールバック
上のプログラムの記述は 1つの処理にまとめて、 下のように書くこともできます。 変数 callback を使わず、関数の引数に直接 function(){...} を書きます。 関数の名前もないので無名関数と呼びます。 このような書き方は JavaScript でよく出てきます。
例: foo( function(param) { //コールバック処理 }); 例: アロー関数で書いた場合 foo( param => コールバック処理 ); コールバック関数の引数が 1個の場合は param に () は不要です。 また、コールバック処理が 1行の場合は { } や return は不要です。
コールバック処理の記述例
- 例1: イベント処理をつける
// id="BUTTON1" の要素を取り出し、ボタンを押したときの処理をつける。 const button = document.querySelector("#BUTTON1"); button.onclick = function(event){ //ボタンを押したときの処理 }; // アロー関数で書いた場合 button.onclick = event => { //ボタンを押したときの処理 };
- 例2: イベント処理をつける (addEventListener の場合)
// id="DIV1" の要素を取り出し、マウスボタンを押したときの処理をつける。 const div = document.querySelector("#DIV1"); div.addEventListener("mousedown", event => { //マウスボタンを押したときの処理 });
- 例3: HTML を読み込んだ直後の初期化処理
// body タグに onload="関数名()" を書くのと同じ処理です。 //1) function で記述 window.onload = function(event) { //初期化処理 }; //2) アロー関数で記述 window.onload = event => { //初期化処理 }; //3) addEventListener() で記述 window.addEventListener("load", event => { //初期化処理 });
コールバック処理が必要になる例
テキストデータを読み取って、 条件を満たす行だけ抽出する処理を考えてみます。 データは下のようなテキストだったとします。
#先頭の文字が # の行はコメント #サンプルデータ 果物、りんご 野菜、玉ネギ 果物、バナナ 魚、さんま 野菜、キャベツ 魚、マグロ
抽出したい行の条件は無数にあり、 例えば下のような条件だったとします。
1) データの行だけ抽出する 2) 果物だけ抽出する 3) 魚だけ抽出する 4) 野菜と魚だけ抽出する 5) ひらがなの名前だけ抽出する 6) 4文字の名前だけ抽出する など
それぞれの処理プログラムはどうなるか?。 概観は下のようになります。
let text = テキストデータを読む; let list = text.split(/\r\n|\n|\r/); //text を行の配列に分割 let result = []; //抽出結果を格納する配列 for (let i=0; i<list.length; i++){ let s = list[i]; if (抽出条件){ result.push(s); //条件を満たす行を result に入れる } } resultを使って次の処理へ上のプログラムで、if (抽出条件) の部分だけ内容が変わるプログラムになります。 抽出条件は例えば下のようになります。
1) 先頭の文字が # でない 2) データ行 AND 「果物」の文字を含む 3) データ行 AND 「魚」の文字を含む 4) データ行 AND 「野菜」または「魚」の文字を含む 以下、省略
抽出条件が変わるたびに 上のようなコードを毎回書いていたら面倒なので、 うまく汎用化する方法を考えます。 ここでコールバック処理を使います。
/* 共通の処理を関数化 isMatch(s) が true を返す行だけ配列に入れて返す。 isMatch : コールバック関数。 isMatch(s); s は行の文字列。戻り値は true/false */ function getDataList(isMatch){ let text = テキストデータを読む; let list = text.split(/\r\n|\n|\r/); let result = []; for (let i=0; i<list.length; i++){ let s = list[i]; if (isMatch(s)){ //関数の引数の isMatch をここで使う。 result.push(s); } } return result; } //抽出処理の記述は下だけで済む let isMatch = function(s){ return (sが抽出条件を満たすか); }; let result = getDataList(isMatch); //簡略化して書くと下のような記述になります。 let result = getDataList( s => (sが抽出条件を満たすか) ); //昔風の書き方 (IE でも動かす場合) let result = getDataList( function(s){ return (sが抽出条件を満たすか); });
毎回、最初のサンプルのようなコードを書くのに比べると簡素になります。
サンプル
配列 Array にも filter(isMatch) 関数があります。
JavaScript: 配列と連想配列
- TestJS_textarea_read_array_filter01.html
Array.filter()のサンプル。 textarea のテキストから条件を満たす行を抽出する。 - TestJS_textlist_filter01.html
上の話をツール化。textarea のテキストから条件を満たす行を抽出する。
ツールサンプル - TestJS_parse_CSV01.html
Array.map() とアロー関数のサンプル。CSV テキストをパースする例。
テキストを2重配列に分割する
文字列の置換処理の例 (2023/11)
文字列の置換処理 String.replace() でも コールバック処理を多用します。
// 例: HTML テキストの <h2> タグに id="ID(通し番号)" の id を付ける。 let counter = 1; html = html.replace(/<h2>/g, () => `<h2 id="ID${ counter++ }">` );