23. februar 2026

Function binding

Når vi videregiver objektmetoder som callbacks, for eksempel til setTimeout, opstår der et kendt problem: “vi mister this”.

I dette kapitel vil vi se de måder, hvorpå du kan fikse det.

Mister “this”

Vi har allerede set eksempler på, hvordan this går tabt. Når en metode videregives til et andet sted udenfor objektet går this tabt.

Her er hvordan det kan ske med setTimeout:

let user = {
  firstName: "Karsten",
  sayHi() {
    alert(`Hej, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hej, undefined!

Som vi kan se, viser output’en ikke “Karsten” som this.firstName, men undefined!

Det er fordi setTimeout modtog funktionen user.sayHi seperaret fra objektet. Den sidste linje kan blive omskrevet som:

let f = user.sayHi;
setTimeout(f, 1000); // mistet user kontekst

Metoden setTimeout i browseren er lidt speciel: den sætter this=window for funktionskaldet (i Node.js bliverthis til et timer-objekt men det er mindre vigtigt her). Så med this.firstName kigger det efter window.firstName som ikke eksisterer. I andre lignende tilfælde vil this fremstå som undefined.

Opgaven er ret typisk – vi vil gerne videregive et objekts metode til et andet sted (her til en planlægger) hvor det så vil blive kaldt. Hvordan sikrer vi os at det vil blive kaldt med den rigtige kontekst?

Løsning 1: En wrapper

Den simple løsning er at bruge en wrapper-funktion:

let user = {
  firstName: "Karsten",
  sayHi() {
    alert(`Hej, ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.sayHi(); // Hej, Karsten!
}, 1000);

Det virker fordi det modtager user fra det ydre leksikale miljø, og kalder metoden normalt.

Det samme, men kortere, kunne være:

setTimeout(() => user.sayHi(), 1000); // Hej, Karsten!

Virker fint, men der er en mindre sårbarhed i kodens struktur.

Hvad hvis user ændrer værdi før setTimeout udløses (der er en sekund forsinkelse!)? Så vil det pludselig kalde det forkerte objekt!

let user = {
  firstName: "Karsten",
  sayHi() {
    alert(`Hej, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ...værdien af user ændres inden der er gået et sekund
user = {
  sayHi() { alert("En anden user i setTimeout!"); }
};

// En anden user i setTimeout!

Den næste løsning garanterer, at sådan noget ikke vil ske.

Løsning 2: bind

Functioner leverer endnu en metode bind som tillader at fikse this.

Den grundlæggend syntaks er:

// der kommer en mere kompleks syntaks lidt senere
let boundFunc = func.bind(context);

Resultatet af func.bind(context) er et specielt funktion-lignende “eksotisk objekt”, som kan kaldes som en funktion og transparent overfører kaldet til func med this=context.

Med andre ord er et kald til boundFunc som et kald til func med et fastsat this.

For eksempel videregiver funcUser et kald til func med this=user:

let user = {
  firstName: "Karsten"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // Karsten

Her er func.bind(user) en “bundet variant” af func med et fastsat this=user.

Alle argumenter bliver videregivet til den originale func som de er, for eksempel:

let user = {
  firstName: "Karsten"
};

function func(phrase) {
  alert(phrase + ', ' + this.firstName);
}

// bind this til user
let funcUser = func.bind(user);

funcUser("Hej"); // Hej, Karsten (argument "Hej" videregives og this=user)

Lad os nu prøve med en metode fra et objekt:

let user = {
  firstName: "Karsten",
  sayHi() {
    alert(`Hej, ${this.firstName}!`);
  }
};

let sayHi = user.sayHi.bind(user); // (*)

// du kan køre det uden et objekt
sayHi(); // Hej, Karsten!

setTimeout(sayHi, 1000); // Hej, Karsten!

// selv hvis værdien af user ændres inden der er gået et sekund
// sayHi bruger den forud bundne værdi som referer til det gamle user-objekt
user = {
  sayHi() { alert("En anden user i setTimeout!"); }
};

I linjen med (*) tager vi metoden user.sayHi og binder den til user. sayHi er en “bundet” funktion der kan kaldes alene eller videregives til setTimeout – det spiller ingen rolle, konteksten vil være korrekt.

Her kan vi se at argumenter videregives “som de er”, kun this er fastsat af bind:

let user = {
  firstName: "Karsten",
  say(phrase) {
    alert(`${phrase}, ${this.firstName}!`);
  }
};

let say = user.say.bind(user);

say("Hej"); // Hej, Karsten! ("Hej" sendes til say)
say("Farvel"); // Farvel, Karsten! ("Farvel" sendes til say)
En bekvemt metode: bindAll

Hvis et objekt har mange metoder og vi planlægger aktivt at sende dem videre, så kan vi binde dem alle i en løkke:

for (let key in user) {
  if (typeof user[key] == 'function') {
    user[key] = user[key].bind(user);
  }
}

JavaScript biblioteker leverer også ofte funktioner til nemt binder mange metoder på én gang, f. eks. _.bindAll(object, methodNames) i lodash.

Delvise funktioner (partial functions)

Indtil videre har vi kun talt om at binde this. Lad os tage det et skridt videre.

Vi kan binde andet end this. Vi kan også binde argumenter. Det er sjældent gjort, men nogle gange kan det være praktisk.

Den fulde syntaks af bind er:

let bound = func.bind(context, [arg1], [arg2], ...);

Det tillader os at binde kontekst som this og startargumenter for funktionen.

Hvis vi f.eks. har en funktion mul(a, b) som ganger to tal:

function mul(a, b) {
  return a * b;
}

Kan vi bruge bind til at skabe en funktion double på basis af mul:

function mul(a, b) {
  return a * b;
}

let double = mul.bind(null, 2);

alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10

Kaldet til mul.bind(null, 2) opretter en ny funktion double der videregiver kald til mul, med null som kontekst og 2 som første argument. Yderligere argumenter videregives “som de er”.

Dette kaldes partial function application – vi opretter en ny funkiton ved at fiksere nogle af parametrene hos den eksisterende funktion.

Bemærk, at vi ikke bruger this her. Men bind kræver den, så vi skal indsætte noget som f. eks. null.

Funktionen triple nedenfor ganger med 3:

function mul(a, b) {
  return a * b;
}

let triple = mul.bind(null, 3);

alert( triple(3) ); // = mul(3, 3) = 9
alert( triple(4) ); // = mul(3, 4) = 12
alert( triple(5) ); // = mul(3, 5) = 15

Hvorfor skulle vi have lyst til at oprette delvise funktioner?

Fordelen er at vi kan oprette en uafhængig funktion med et læsbart navn (double, triple). Vi kan bruge den og ikke behøve at give det første argument hver gang, da det er fastsat med bind.

I andre tilfælde er delvise funktioner nyttige, når vi har en meget generisk funktion og ønsker en mindre universel variant af den for nemhedens skyld.

Et eksempel kunne være en funktion send(from, to, text). Inden for et user-objekt kan vi ønske at bruge en delvis variant af den: sendTo(to, text) som sender fra det nuværende brugerobjekt.

Arbejde partielt uden kontekst

Hvad hvis vi gerne vil fiksere nogle argumenter, men ikke konteksten this? For eksempel for en metode i et objekt.

Den indbyggede bind tillader ikke det. Vi kan ikke bare udelade konteksten og springe til argumenter.

Heldigvis kan en funktion (nedenfor kaldt partial) der kun binder argumenter let implementeres.

Sådan her:

function partial(func, ...argsBound) {
  return function(...args) { // (*)
    return func.call(this, ...argsBound, ...args);
  }
}

// Usage:
let user = {
  firstName: "John",
  say(time, phrase) {
    alert(`[${time}] ${this.firstName}: ${phrase}!`);
  }
};

// Tilføj en partial metode med fikseret tidsstempel
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());

user.sayNow("Hej");
// Noget i stil med:
// [10:00] John: Hej!

Resultatet af partial(func[, arg1, arg2...]) kaldet er en wrapper (*) der kalder func med:

  • Samme this som den får (for user.sayNow kaldet er det user)
  • Derefter giver den det ...argsBound – argumenter fra kaldet til partial ("10:00")
  • Derefter giver den det ...args – argumenter givet til wrapperen ("Hej")

Så nemt at gøre det med spread syntax, ikke?

Der findes en færdiglavet _.partial implementering i lodash biblioteket.

Opsummering

Metoden func.bind(context, ...args) returnerer en “bundet variant” af funktionen func som fikserer konteksten this og de første argumenter hvis givet.

Ofte bruger vi bind til at fiksere this for en objektmetode, så vi kan videregive den et sted. For eksempel til setTimeout.

Når vi fikserer nogle argumenter af en eksisterende funktion, kaldes den skabte (mindre universelle) funktion partielly applied eller partial.

‘Partials’ er praktiske når vi vil undgå at gentage det samme argument igen og igen. For eksempel hvis vi har en send(from, to) funktion, og from altid skal være det samme for vores opgave, kan vi oprette en ‘partial’ og fortsætte med den.

Opgaver

vigtighed: 5

Hvad vil output’et være?

function f() {
  alert( this ); // ?
}

let user = {
  g: f.bind(null)
};

user.g();

Svaret er: null.

function f() {
  alert( this ); // null
}

let user = {
  g: f.bind(null)
};

user.g();

Konteksten for en bundet funktion er fikseret. Der er ingen måde at ændre den yderligere.

Så selv når vi kører user.g(), kaldes den originale funktion med this=null.

vigtighed: 5

Kan vi ændre this ved yderligere binding?

Hvad vil output’et være?

function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Ann" } );

f();

Svaret er: John.

function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Pete"} );

f(); // John

Det eksotiske bundne objekt der returneres af f.bind(...) husker den kontekst (og eventuelle argumenter) den fik da den blev oprettet.

En sådan funktion kan ikke blive bundet igen.

vigtighed: 5

Der er en værdi i en egenskab af en funktion. Vil den ændre sig efter bind? Hvorfor, eller hvorfor ikke?

function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "John"
});

alert( bound.test ); // hvad vil output'et være? hvorfor?

Svaret er: undefined.

Resultatet af bind er et andet objekt. Det har ikke egenskaben test.

vigtighed: 5

Kaldet til askPassword() i koden nedenfor skal tjekke passwordet og derefter kalde user.loginOk/loginFail afhængigt af svaret.

Men det fører til en fejl. Hvorfor?

Fix den fremhævede linje for at alt skal virke korrekt (andre linjer skal ikke ændres).

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`${this.name} logget ind`);
  },

  loginFail() {
    alert(`${this.name} fejl i log in`);
  },

};

askPassword(user.loginOk, user.loginFail);

Fejlen sker fordi askPassword får funktionerne loginOk/loginFail uden objektet.

Når den kalder dem, antager de this=undefined.

Lad os binde konteksten:

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`${this.name} logget ind`);
  },

  loginFail() {
    alert(`${this.name} fejl i log in`);
  },

};

askPassword(user.loginOk.bind(user), user.loginFail.bind(user));

Nu virker det.

En alternativ løsning kan være at bruge en wrapper-funktion:

//...
askPassword(() => user.loginOk(), () => user.loginFail());

Det virker normalt også fint og ser ok ud.

Det er lidt mindre pålideligt i mere komplekse situationer hvor user variablen kan ændre sig efter askPassword er kaldt, men før besøgende har svaret og kalder () => user.loginOk().

vigtighed: 5

Denne opgave er en lidt mere kompleks variant af Fix en funktion der mister "this".

user objektet er ændret. Nu har den, i stedet for to funktioner loginOk/loginFail, én enkelt funktion user.login(true/false).

Hvad skal vi sende til askPassword i koden nedenfor, så den kalder user.login(true) som ok og user.login(false) som fail?

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  login(result) {
    alert( this.name + (result ? ' logged in' : ' failed to log in') );
  }
};

askPassword(?, ?); // ?

Din ændring skal kun ændre den fremhævede linje.

  1. Brug enten en wrapper funktion, en arrow for at gøre det helt kort:

    askPassword(() => user.login(true), () => user.login(false));

    Nu henter user fra ydre variable og kører normalt.

  2. eller opret en delvis funktion fra user.login der bruger user som kontekst og det korrekte første argument:

    askPassword(user.login.bind(user, true), user.login.bind(user, false));
Tutorial-oversigt

Kommentarer

læs dette før du kommenterer…
  • Hvis du har forslag til forbedringer - så opret venligst et GitHub-issue eller en pull request i stedet for at kommentere.
  • Hvis du ikke forstår noget i artiklen - så uddyb venligst.
  • For at indsætte få ord kode, brug <code>-taggen, for flere linjer - omslut dem i <pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepen…)