サーチ…
単純なコールバックの使用例
コールバックは、コードを変更することなく 、関数(またはメソッド)の機能を拡張する方法を提供します。このアプローチは、モジュール(ライブラリ/プラグイン)で頻繁に使用されますが、そのコードは変更される予定はありません。
指定された値の配列の合計を計算して、次の関数を記述したとします。
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
ここで、配列の各値を使って何かをしたいとしましょう。たとえば、 alert()
を使って表示します。 foo
のコードを以下のように適切に変更することができます:
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
alert(array[i]);
sum += array[i];
}
return sum;
}
しかし、 alert()
代わりにconsole.log
を使用するとどうなるでしょうか? foo
のコードを明らかに変更すると、それぞれの値で何か他のことをすると決めると、良い考えではありません。 foo
のコードを変更することなく、私たちの心を変えるオプションを持つ方がはるかに良いです。これはまさにコールバックのユースケースです。私たちはfoo
の署名とボディをわずかに変更する必要があります:
function foo(array, callback) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
callback(array[i]);
sum += array[i];
}
return sum;
}
そして、パラメータを変更するだけで、 foo
の動作を変更することができます:
var array = [];
foo(array, alert);
foo(array, function (x) {
console.log(x);
});
非同期関数を使用した例
jQueryでは、JSONデータを取得する$.getJSON()
メソッドは非同期です。したがって、コールバックでコードを渡すと、JSONがフェッチされた後にコードが呼び出されます。
$.getJSON()
構文:
$.getJSON( url, dataObject, successCallback );
$.getJSON()
コードの例:
$.getJSON("foo.json", {}, function(data) {
// data handling code
});
データが実際に受信される前に、ため、データ処理コードの可能性が高い、と呼ばれることになるので、以下では、動作しません $.getJSON
機能は、時間の不特定の長さを取り、それはJSONを待ってコールスタックを保持していません。
$.getJSON("foo.json", {});
// data handling code
非同期関数の別の例は、jQueryのanimate()
関数です。アニメーションを実行するには特定の時間がかかるため、アニメーションの直後に直接コードを実行することが望ましい場合もあります。
.animate()
構文:
jQueryElement.animate( properties, duration, callback );
例えば、要素が完全に消えた後のフェードアウトアニメーションを作成するには、次のコードを実行します。コールバックの使用に注意してください。
elem.animate( { opacity: 0 }, 5000, function() {
elem.hide();
} );
これにより、関数の実行が終了した直後に要素を隠すことができます。これは以下とは異なります:
elem.animate( { opacity: 0 }, 5000 );
elem.hide();
後者はanimate()
(非同期関数)が完了するのを待たず、そのため要素がすぐに隠され、望ましくない結果が生じるからです。
コールバックとは何ですか?
これは通常の関数呼び出しです。
console.log("Hello World!");
通常の関数を呼び出すと、その関数が呼び出し元に戻ります。
ただし、関数を呼び出し元に戻す必要がある場合があります。
[1,2,3].map(function double(x) {
return 2 * x;
});
上記の例では、関数double
は関数map
コールバックです。理由は次のとおりです。
- 関数
double
は、呼び出し元によって関数map
与えられます。 - 関数
map
は、そのジョブを実行するために関数double
0回以上呼び出す必要があります。
したがって、関数map
は関数double
呼び出すたびに呼び出し元に制御を戻します。したがって、名前は "コールバック"です。
関数は複数のコールバックを受け入れることができます:
promise.then(function onFulfilled(value) {
console.log("Fulfilled with value " + value);
}, function onRejected(reason) {
console.log("Rejected with reason " + reason);
});
ここでは、関数は、 then
2つのコールバック関数、受け入れonFulfilled
とonRejected
。さらに、これらの2つのコールバック関数のうちの1つだけが実際に呼び出されます。
何より興味深いのは、機能があることをthen
のコールバックのいずれかが呼び出される前に戻ります。したがって、元の関数が返された後でもコールバック関数が呼び出されることがあります。
継続(同期および非同期)
コールバックを使用すると、メソッドが完了した後に実行されるコードを提供できます。
/** * @arg {Function} then continuation callback */ function doSomething(then) { console.log('Doing something'); then(); } // Do something, then execute callback to log 'done' doSomething(function () { console.log('Done'); }); console.log('Doing something else'); // Outputs: // "Doing something" // "Done" // "Doing something else"
上記のdoSomething()
メソッドは、 doSomething()
が返されるまでコールバック実行ブロックと同期して実行され、インタプリタが移動する前にコールバックが確実に実行されます。
コールバックを使用してコードを非同期で実行することもできます。
doSomethingAsync(then) { setTimeout(then, 1000); console.log('Doing something asynchronously'); } doSomethingAsync(function() { console.log('Done'); }); console.log('Doing something else'); // Outputs: // "Doing something asynchronously" // "Doing something else" // "Done"
then
のコールバックが考慮される継続doSomething()
メソッド。関数内の最後の命令としてコールバックを提供することは、 テールコールと呼ばれ、ES2015インタープリタによって最適化されます。
エラー処理と制御フローの分岐
コールバックはしばしばエラー処理を提供するために使用されます。これは制御フロー分岐の一形態であり、エラーが発生したときにのみ実行される命令もあります。
const expected = true;
function compare(actual, success, failure) {
if (actual === expected) {
success();
} else {
failure();
}
}
function onSuccess() {
console.log('Value was expected');
}
function onFailure() {
console.log('Value was unexpected/exceptional');
}
compare(true, onSuccess, onFailure);
compare(false, onSuccess, onFailure);
// Outputs:
// "Value was expected"
// "Value was unexpected/exceptional"
上記のcompare()
コード実行には、期待値と実際の値が同じ場合のsuccess
と異なる場合の2つの分岐がありerror
。これは、制御フローが非同期命令の後に分岐する必要がある場合に特に便利です。
function compareAsync(actual, success, failure) {
setTimeout(function () {
compare(actual, success, failure)
}, 1000);
}
compareAsync(true, onSuccess, onFailure);
compareAsync(false, onSuccess, onFailure);
console.log('Doing something else');
// Outputs:
// "Doing something else"
// "Value was expected"
// "Value was unexpected/exceptional"
複数のコールバックは相互に排他的である必要はなく、両方のメソッドを呼び出すことができることに注意してください。同様に、 compare()
は省略可能なコールバックを使用して記述することができます(デフォルト値としてnoopを使用します - ヌルオブジェクトパターンを参照)。
コールバックと `this`
コールバックを使用すると、特定のコンテキストにアクセスしたいことがよくあります。
function SomeClass(msg, elem) {
this.msg = msg;
elem.addEventListener('click', function() {
console.log(this.msg); // <= will fail because "this" is undefined
});
}
var s = new SomeClass("hello", someElement);
ソリューション
bind
使用bind
bind
効果的にbind
に渡されたものにthis
値を設定し、元の関数を呼び出す新しい関数を生成します。function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', function() { console.log(this.msg); }.bind(this)); // <=- bind the function to `this` }
矢印関数を使用する
アロー機能は自動的に現在のバインド
this
コンテキストを。function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click',() => { // <=- arrow function binds `this` console.log(this.msg); }); }
多くの場合、メンバ関数を呼び出して、理想的にはイベントに渡された引数を関数に渡したいと思うことがあります。
ソリューション:
バインドを使用する
function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', this.handleClick.bind(this)); } SomeClass.prototype.handleClick = function(event) { console.log(event.type, this.msg); };
矢印関数と残りの演算子を使用する
function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', (...a) => this.handleClick(...a)); } SomeClass.prototype.handleClick = function(event) { console.log(event.type, this.msg); };
特にDOMイベントリスナの場合、
EventListener
インタフェースを実装できますfunction SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', this); } SomeClass.prototype.handleEvent = function(event) { var fn = this[event.type]; if (fn) { fn.apply(this, arguments); } }; SomeClass.prototype.click = function(event) { console.log(this.msg); };
矢印機能を使用したコールバック
矢印関数をコールバック関数として使用すると、コード行を減らすことができます。
arrow関数のデフォルトの構文は次のとおりです。
() => {}
これはコールバックとして使用できます
たとえば、配列[1,2,3,4,5]内のすべての要素を出力する場合は、
矢印機能なしでは、コードは次のようになります
[1,2,3,4,5].forEach(function(x){
console.log(x);
}
矢印機能を使用すると、
[1,2,3,4,5].forEach(x => console.log(x));
ここでコールバック関数function(x){console.log(x)}
はx=>console.log(x)
縮小されていx=>console.log(x)