Sök…
Enkla exempel på återuppringning
Återuppringningar erbjuder ett sätt att utöka funktionaliteten i en funktion (eller metod) utan att ändra dess kod. Denna metod används ofta i moduler (bibliotek / plugins), vars kod inte ska förändras.
Anta att vi har skrivit följande funktion och beräknar summan av en given mängd värden:
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
sum += array[i];
}
return sum;
}
Anta nu att vi vill göra något med varje värde i matrisen, t.ex. visa det med alert()
. Vi kan göra lämpliga ändringar i foo
koden, så här:
function foo(array) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
alert(array[i]);
sum += array[i];
}
return sum;
}
Men vad händer om vi bestämmer oss för att använda console.log
istället för alert()
? Det är uppenbart att det inte är bra att ändra foo
koden, när vi bestämmer oss för att göra något annat med varje värde. Det är mycket bättre att ha möjlighet att ändra tanken utan att ändra foo
koden. Det är exakt användningsfallet för återuppringningar. Vi måste bara ändra foo
signatur och kropp:
function foo(array, callback) {
var sum = 0;
for (var i = 0; i < array.length; i++) {
callback(array[i]);
sum += array[i];
}
return sum;
}
Och nu har vi möjlighet att förändra beteendet hos foo
bara genom att ändra dess parametrar:
var array = [];
foo(array, alert);
foo(array, function (x) {
console.log(x);
});
Exempel med asynkrona funktioner
I jQuery är $.getJSON()
för att hämta JSON-data asynkron. Därför säkerställer kod i en återuppringning att koden kallas efter att JSON har hämtats.
$.getJSON()
syntax:
$.getJSON( url, dataObject, successCallback );
Exempel på $.getJSON()
:
$.getJSON("foo.json", {}, function(data) {
// data handling code
});
Följande skulle inte fungera, eftersom uppgifterna hanteringskoden skulle sannolikt att kallas innan data faktiskt fått, eftersom $.getJSON
funktionen tar en ospecificerad tid och inte hålla upp anropsstacken när den väntar på JSON.
$.getJSON("foo.json", {});
// data handling code
Ett annat exempel på en asynkron funktion är jQuerys animate()
-funktion. Eftersom det tar en viss tid att köra animationen är det ibland önskvärt att köra en kod direkt efter animationen.
.animate()
syntax:
jQueryElement.animate( properties, duration, callback );
Till exempel, för att skapa en fading-out animation varefter elementet försvinner helt, kan följande kod köras. Notera användningen av återuppringningen.
elem.animate( { opacity: 0 }, 5000, function() {
elem.hide();
} );
Detta gör att elementet kan döljas direkt efter att funktionen är klar. Detta skiljer sig från:
elem.animate( { opacity: 0 }, 5000 );
elem.hide();
eftersom den senare inte väntar på att animate()
(en asynkron funktion) ska slutföras, och därför är elementet gömt direkt, vilket ger en oönskad effekt.
Vad är ett återuppringning?
Detta är ett normalt funktionssamtal:
console.log("Hello World!");
När du ringer till en normal funktion gör det sitt jobb och returnerar sedan kontrollen tillbaka till den som ringer.
Men ibland måste en funktion returnera kontrollen tillbaka till den som ringer för att göra sitt jobb:
[1,2,3].map(function double(x) {
return 2 * x;
});
I exemplet ovan, funktionen double
är en callback för funktionen map
eftersom:
- Funktionen
double
ges till funktionenmap
av den som ringer. - Funktionen
map
måste anropa funktionendouble
noll eller flera gånger för att göra sitt jobb.
Således funktionen map
i huvudsak åter kontrollen tillbaka till den som ringer varje gång man kallar funktionen double
. Därför namnet "återuppringning".
Funktioner kan acceptera mer än ett återuppringning:
promise.then(function onFulfilled(value) {
console.log("Fulfilled with value " + value);
}, function onRejected(reason) {
console.log("Rejected with reason " + reason);
});
Här accepterar sedan funktionen then
två återuppringningsfunktioner, onFulfilled
och onRejected
. Dessutom kallas bara en av dessa två återuppringningsfunktioner faktiskt.
Det som är mer intressant är att funktionen then
återgår innan någon av återuppringningarna anropas. Därför kan en återuppringningsfunktion anropas även efter att den ursprungliga funktionen har återgått.
Fortsättning (synkron och asynkron)
Återuppringningar kan användas för att tillhandahålla kod som ska köras efter att en metod har slutförts:
/** * @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()
ovan körs synkront med återuppringning - exekveringsblock tills doSomething()
återgår, vilket säkerställer att återuppringningen körs innan tolkar går vidare.
Återuppringningar kan också användas för att köra kod asynkront:
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"
De then
återuppringningarna betraktas som fortsättningar på doSomething()
-metoderna. Att tillhandahålla ett återuppringning som den senaste instruktionen i en funktion kallas ett svarssamtal , vilket optimeras av ES2015-tolkar .
Felhantering och kontrollflödesgrenning
Återuppringningar används ofta för att tillhandahålla felhantering. Detta är en form av kontrollflödesgrenning, där vissa instruktioner utförs endast när ett fel uppstår:
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"
Kodkörning i compare()
ovan har två möjliga grenar: success
när de förväntade och faktiska värdena är desamma och error
när de är olika. Detta är särskilt användbart när kontrollflödet ska förgrena sig efter någon asynkron instruktion:
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"
Det bör noteras att flera återuppringningar inte behöver vara uteslutande - båda metoderna kan kallas. På liknande sätt kan compare()
skrivas med återuppringningar som är valfria (genom att använda en noop som standardvärde - se Nollobjektmönster ).
Återuppringningar och "detta"
Ofta när du använder ett återuppringning vill du ha åtkomst till ett specifikt sammanhang.
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);
lösningar
Använd
bind
bind
genererar effektivt en ny funktion som sätterthis
till vad som skickades för attbind
kallar sedan den ursprungliga funktionen.function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click', function() { console.log(this.msg); }.bind(this)); // <=- bind the function to `this` }
Använd pilfunktioner
Pilfunktioner binder automatiskt det nuvarande
this
sammanhang.function SomeClass(msg, elem) { this.msg = msg; elem.addEventListener('click',() => { // <=- arrow function binds `this` console.log(this.msg); }); }
Ofta vill du ringa en medlemsfunktion och helst överföra alla argument som överförts till händelsen till funktionen.
lösningar:
Använd bind
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); };
Använd pilfunktioner och viloperatören
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); };
Speciellt för DOM-händelselister kan du implementera
EventListener
gränssnittetfunction 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); };
Återuppringning med pilfunktionen
Att använda pilfunktionen som återuppringningsfunktion kan minska kodraderna.
Standardsyntaxen för pilfunktionen är
() => {}
Detta kan användas som återuppringningar
Om vi till exempel vill skriva ut alla element i en matris [1,2,3,4,5]
utan pilfunktion kommer koden att se ut så här
[1,2,3,4,5].forEach(function(x){
console.log(x);
}
Med pilfunktionen kan den reduceras till
[1,2,3,4,5].forEach(x => console.log(x));
Här reduceras återuppringningsfunktionen function(x){console.log(x)}
till x=>console.log(x)