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:

  1. Funktionen double ges till funktionen map av den som ringer.
  2. Funktionen map måste anropa funktionen double 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ätter this till vad som skickades för att bind 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änssnittet

      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);
      };
    

Å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)



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow