JavaScript bind.apply , wie geht das?

Hendoul

Commander
Registriert
Apr. 2008
Beiträge
2.158
Hi :)

Kann mir jemand diese Syntax erklären?

Javascript:
SpyStrategy.prototype.exec = function(context, args, invokeNew) {
    var contextArgs = [context].concat(
      args ? Array.prototype.slice.call(args) : []
    );
    var target = this.plan.bind.apply(this.plan, contextArgs);

    return invokeNew ? new target() : target();
  };

Und zwar diese Zeile:
var target = this.plan.bind.apply(this.plan, contextArgs);

Ich verstehe nicht, warum man bind ohne Klammern hier nutzen kann. Also nicht so this.plan.bind(...).apply(...)
bind gibt ja eine neue Funktion zurück, aber ohne Klammern ist es doch nur die Funktionsdeklaration?
 
Was bind bedeutet bzw. was es ist, hängt hier maßgeblich von plan ab. Was plan ist, kann ich anhand des Schnipsels nicht beurteilen. Oder anhand fehlener JS-Kenntnisse.

Gegenfrage: Wie kommst du auf die Behauptung, dass bind eine neue Funktion zurückliefert?

Ich nehme an, bei dir ist dann plan ähnlich noch definiert wie hier das declare:
Code:
LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};
(Quelle: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function/bind )
 
plan ist eine Funktion
 
Sieh dir mal an was Prototyping bedeutet.

Was bind angeht: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
Was apply angeht: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

Zur Erklärung:
TL;DR:
  • apply() wird genutzt, weil man hiermit ein Array als Parameter übergeben kann, Gegenstück ist call() mit variadischen Parametern
  • bind() wird genutzt um einen neuen Callback mit identischer Implementierung zu bekommen, wo this entsprechend gebunden wird
  • schlussendlich erhält alles this.plan, welches eine geänderte this Referenz besitzt und dynamische Parameter erhält

Andere/neuere Schreibweise mit Unpacking (was allerdings nicht jede Engine unterstützen muss):
Code:
this.plan.bind( this.plan, ...contextArgs )
Beispiel: https://jsfiddle.net/Lqdymsfp/
Javascript:
let called = 0
function foo()
{
    console.log( {
        called: called++,
        "this.name": this.constructor.name,
        "this": this,
        "arguments": arguments
  })
}

foo() // called = 0
foo("a", 1, true) // called =1
foo("b", 2, false) // called =2

;(foo.bind( "a", 1, true ))() // called =3
;(foo.bind( "b", 2, false ))() // called =4

foo.apply( "a", [1, true] ) // called =5
foo.apply( "b", [2, false] ) // called =6

;(foo.bind.apply( foo, ["a", 1, true] ))() // 7
;(foo.bind.apply( foo, ["b", 2, false] ))() // 8
In 0 - 2 ist this entsprechend an window gebunden, 3 - 8 haben ein geändertes this.

Das Konstrukt ist primär veraltete/mit älteren Versionen kompatible Syntax. In neueren Versionen könnte man es via Unpacking machen. Aber da man JS gut transpilen und neuere Versionen einfach mit älteren kompatibel machen kann (siehe Babel), kommt halt sowas rum.

Dein Code ist eher ein suboptimales Beispiel. Eher nutzt man sowas bei Event Handlern u.ä.

Beispiel: https://jsfiddle.net/buj8gL6f/
Javascript:
const handler = function()
{
    console.log( {
        "name": this.myName,
        "arguments": arguments
  } )
}

const myClass = function()
{
    this.myName = "myObject with a name"
}
const myObject = new myClass()

const args = ["these","are","the","arguments"]

const oldInvocation = handler.bind.apply( handler, [myObject].concat(args) )
oldInvocation()

const newInvocation = handler.bind( myObject, ...args )
newInvocation()

const directInvocationWithApply = handler.apply( myObject, args )

const directInvocationWithCall = handler.call( myObject, ...args )

const deferedInvocation = handler.bind( myObject, ...args )
deferedInvocation()
Vielleicht fragst du dich, warum bind() und nicht einfach nur apply() bzw. call(), schließlich kann ich bei beiden die this-Referenz übergeben. Das siehst du an deinem return. Während ich bei bind() den eigentlichen Aufruf nach hinten verschieben kann und nur den neuen Callback bekomme, wird bei apply() oder call() direkt aufgerufen, was ich ggf. nicht implizit haben will. Bei deinem Code ist es bspw. variabel anhand von invokeNew.
 
  • Gefällt mir
Reaktionen: netzgestaltung und tollertyp
Ich verstehs immer noch nicht. Für mich ist nach wie vor unklar was ein .bind ohne () für eine Wirkung hat.
Nach meinem Verständnis nach gibt ein .bind einfach die Referenz auf die Funktion bind zurück, wird aber gar nicht aufgerufen?

Was bind, call und apply machen ist mir im einzelnen klar. Ich habe bis anhin einfach noch nie ein bind ohne wirklichen Aufruf angetroffen.
 
Hendoul schrieb:
Ich verstehs immer noch nicht. Für mich ist nach wie vor unklar was ein .bind ohne () für eine Wirkung hat.
Yuuri schrieb:
bind() wird genutzt um einen neuen Callback mit identischer Implementierung zu bekommen, wo this entsprechend gebunden wird
Hendoul schrieb:
Nach meinem Verständnis nach gibt ein .bind einfach die Referenz auf die Funktion bind zurück, wird aber gar nicht aufgerufen?
Die Referenz auf die Funktion bekommst du mit
Javascript:
this.plan.bind
Durch apply() wird aber bind() mit entsprechenden Parametern aufgerufen.
 
Aber wo ist der Unterschied zwischen:
Javascript:
var target = this.plan.bind.apply(this.plan, contextArgs);
und
Javascript:
var target = this.plan.apply(this.plan, contextArgs);

aufgerufen wird apply und somit die eigentliche Funktion ja so oder so? Wozu also noch das bind?

und apply ruft dann ja die Funktion auf und speichert das Resultat in target.
Und dann wird in jedem Fall target() aufgerufen, entweder mit new oder ohne. Aber es wird aufgerufen, sprich bei target muss es sich um eine Funktion handeln?
 
Zuletzt bearbeitet:
Ja eben, wozu brauche ich die Referenz, ich kann doch grad so gut einfach direkt apply aufrufen? Sehe nicht ein, warum es das bind dazwischen braucht.
 
Unterschiedliche Aufrufe, die zum selben Ergebnis führen, hab ich dir auch gegeben. Es ist halt ein Pattern, nicht mehr nicht weniger. Und irgendwie gab es wohl nen Grund dafür, das so abzuändern.

Sieh dir den letzten Call deferedInvocation genau an.

Und früher wurde das auch so gemacht.

https://github.com/jasmine/jasmine/...3721ad5e7cf7/src/core/SpyStrategy.js#L93-L101
Javascript:
SpyStrategy.prototype.exec = function(context, args) {
  return this.plan.apply(context, args);
};
Der aktuelle Stand sieht aber eben anders aus.

Wenn du wissen willst warum, arbeite die Commits durch.
 
Du weißt was fn.bind(thisArg[, arg1[, arg2[, ...]]]) prinzipiell macht. Weißt du auch, was die Parameter nach dem ersten machen?

Jedes weitere Argument wird als Parameter beim Aufruf von fn durch die bound function genutzt. Was machst du jetzt aber wenn du nicht weißt, wie viele bzw welche Parameter es gibt? Du nutzt apply, da du dort die zusätzlichen Parameter als ein einzelnes Array übergibst.

Weiterer Versuch es dir zu erklären: Heute kannst du den Spread-Operator nutzen. Früher ging das nicht. Der Spread-Operator wird mit .apply() transpiliert.

Folgende zwei Dinge sind also effektiv identisch. Einmal in altem JS, einmal in modernem:

Javascript:
target = this.plan.bind.apply(this.plan, contextArgs);
Javascript:
target = () => this.plan(...contextArgs);

Also letzlich erlaubt es dir einfach .bind() mit einer beliebigen anzahl an Parametern aufzurufen, so wie man es ohne Spread-Operator mit jeder anderen Funktion auch machen kann.
 
Zuletzt bearbeitet:
Ich habe jetzt auch länger gebraucht um zu verstehen was es damit auf sich hat: apply() ist eine prototype-Funktion eines JS objects, d.h. in this.plan.bind.apply() führt apply() zum Funktionsaufruf von bind, daher zunächst die Verwirrung um die fehlenden Klammern. Und letztlich ist bind hier eigentlich überflüssig weil auch apply() schon einen "bind" auf this.plan als this durchführt.
Richtig?
 
floq0r schrieb:
Nein. Bei plan.apply() wird die Funktion plan direkt aufgerufen. Bei plan.bind() nicht. Bei plan.bind.apply() ebenfalls nicht, da wird nur plan.bind durch apply() aufgerufen.
 
  • Gefällt mir
Reaktionen: floq0r
floq0r schrieb:
Und letztlich ist bind hier eigentlich überflüssig weil auch apply() schon einen "bind" auf this.plan als this durchführt.
Hier ist bind aber nicht überflüssig. Ohne das würdest du plan direkt ausführen.
 
Ok, langsam begreife ich es :D

Aber was mir noch unklar ist von einem deiner Beispiele:
Javascript:
const handler = function()
{
  console.log(this)
  console.log( this.myName)
  console.log(arguments)
}

const myClass = function()
{
    this.myName = "myObject with a name"
}
const myObject = new myClass()

const args = ['these', 'are', 'the', 'arguments']

const oldInvocation = handler.bind.apply( handler, [myObject].concat(args) )
oldInvocation()

Man ruft ja hier die bind Methode via apply auf. Warum muss man hier die Funktion 'handler' mitgeben als this? Für mich würde bind.apply(myObject...) mehr Sinn ergeben. Aber dann heisst es man darf bind nur auf einer Funktion aufrufen und ich verstehe nicht, was das mit dem thisArg zu tun hat.

Desweiteren ist mir nicht klar was es mit dem [myObject].concat(args) auf sich hat.
Das ergibt doch einfach ein Array das so aussieht -> [myObject, 'these', 'are', 'the', 'arguments']
Warum taucht das myObject aber nicht auf wenn man die arguments logt? Und warum ist this dann myObject und nicht der Handler selbst? Mir brummt der Kopf...
 
Du brauchst in apply den handler, da bind sonst nicht weiß, welche Funktion es binden soll. handler ist im apply-Aufruf ja kein Parameter für bind, sondern die Bindung für this innerhalb des Aufrufs von bind.

Genau, [myObject].concat(args) erstellt einfach ein Array mit allem drin. myObject taucht in arguments nicht auf, weil das hier das erste Argument für den Aufruf von bind ist, was da ja wiederum als Bindung für this von handler genutzt wird und nicht als Parameter für den späteren Aufruf von handler.
 
Zurück
Oben