2014-10-24 21 views
36

Dal MDN: metodo diConcatenamento .bind() chiama in JavaScript. Risultato inaspettato?

Il bind() crea una nuova funzione che, quando viene chiamato, ha la sua questa parola chiave impostata sul valore

fornito e posso tranquillamente vederlo lavorare in questo esempio:

(function() { 
    console.log(this); 
}).bind({foo:"bar"})(); 

quali registri Object { foo="bar"}.

Ma se concateno un'altra chiamata di bind o anche una chiamata di "chiamata", sto ancora ricevendo la funzione invocata con "questo" assegnato all'oggetto passato al primo bind. Esempi:

(function() { 
    console.log(this); 
}).bind({foo:"bar"}).bind({oof:"rab"})(); 

&

(function() { 
    console.log(this); 
}).bind({foo:"bar"}).call({oof:"rab"}); 

Sia log Object { foo="bar"} invece di quello che mi sarei aspettato: Object { oof="rab"}.

Indipendentemente dal numero di chiamate impegnative, il primo sembra avere un effetto.

Perché?

Questo potrebbe aiutare. Ho appena scoperto che la versione di jQuery si comporta allo stesso modo! : O

jQuery.proxy(
    jQuery.proxy(function() { 
     console.log(this); 
    },{foo:"bar"}) 
,{oof:"rab"})(); 

tronchi Object { foo="bar"}

+3

Strano! Bella domanda – Jivings

+0

Il sito Web di ecma è inattivo ... Qualcuno conosce un mirror per le specifiche ECMAScript Web? – Jivings

+2

Ottima domanda. Così buono, infatti, ha messo offline la specifica ES5. Penso che tu abbia infranto JavaScript. – joews

risposta

41

Si è tentati di pensare bind come in qualche modo modifica una funzione di utilizzare un nuovo this. In questa interpretazione (errata), la gente pensa a bind aggiungendo una sorta di bandiera magica alla funzione che gli dice di usare un altro this la prossima volta che viene chiamato. Se così fosse, allora dovrebbe essere possibile "scavalcare" e cambiare la bandiera magica. E si potrebbe quindi chiedere, qual è la ragione per limitare arbitrariamente la capacità di farlo?

Ma in effetti, questo è non come funziona.bind crea e restituisce una nuova funzione che quando chiamato richiama la prima funzione con un particolare this. Il comportamento di questa funzione appena creata, per utilizzare lo this specificato per chiamare la funzione originale, è masterizzato in quando viene creata la funzione. Non può essere modificato più di quanto gli interni di qualsiasi altra funzione restituita da una funzione potrebbero essere modificati dopo il fatto.

Può aiutare a guardare un vero e proprio semplice implementazione di bind:

// NOT the real bind; just an example 
Function.prototype.bind = function(ctxt) { 
    var fn = this; 
    return function bound_fn() { 
     return fn.apply(ctxt, arguments); 
    }; 
} 

my_bound_fn = original_fn.bind(obj); 

Come si può vedere, in nessuna parte bound_fn, la funzione tornato da bind, fa riferimento al this con cui la funzione legata è stato chiamato. E 'ignorato, in modo che

my_bound_fn.call(999, arg)   // 999 is ignored 

o

obj = { fn: function() { console.log(this); } }; 
obj.fn = obj.fn.bind(other_obj); 
obj.fn();       // outputs other_obj; obj is ignored 

così posso legare la funzione tornato da bind "nuovo", ma che è non rebinding la funzione originaria; si limita a legare la funzione esterna, che non ha alcun effetto sulla funzione interna, poiché è già impostata per chiamare la funzione sottostante con il contesto (valore this) passata a bind. Riesco a legare ancora e ancora, ma tutto ciò che finisco per fare è creare più funzioni esterne che possono essere associate a qualcosa ma che alla fine finiscono per chiamare la funzione più interna restituita dal primo bind.

Pertanto, è in qualche modo fuorviante dire che bind "non può essere sovrascritto".

Se si desidera "riassociare" una funzione, è possibile eseguire una nuova rilegatura sulla funzione originale. Quindi, se ho legato una volta:

function orig() { } 
my_bound_fn = orig.bind(my_obj); 

e poi voglio organizzare per la mia funzione originaria per essere chiamato con qualche altro this, allora non associare nuovamente la funzione legata:

my_bound_fn = my_bound_fn.bind(my_other_obj);  // No effect 

Invece, ho appena creare una nuova funzione legata a quella originale: risposta eccellente

my_other_bound_fn = orig.bind(my_other_obj); 
12

ho trovato questa linea su MDN:

Il bind() funzione crea una nuova funzione (una funzione legata) con lo stesso corpo della funzione (interno chiamare la proprietà in termini ECMAScript 5) come la funzione che viene chiamata (la funzione target della funzione associata ) con questo valore associato al primo argomento di bind(), che non può essere sovrascritto.

quindi forse non è davvero possibile ignorarlo una volta impostato.

+0

Sarebbe troppo sfacciato chiedere perché non può essere cancellato? –

+2

è qualcosa che non so, e spero che qualche persona saggia faccia luce su questo –

+0

Suvviato, accetterà se quella persona saggia non compare :) –

3

Okay, questo sarà per lo più speculazioni ma proverò a ragionare su di esso.

La specifica ECMAScript (che è attualmente in giù) afferma quanto segue per la funzione bind (enfasi mia):

15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, ...]])

Il metodo bind richiede uno o più argomenti, thisArg e (opzionalmente) arg1, arg2, ecc, e restituisce un nuovo oggetto funzione eseguendo il seguente passaggi:

  1. Lascia che sia Target questo valore.
  2. Se IsCallable (Target) è false, genera un'eccezione TypeError.
  3. Sia A un nuovo (eventualmente vuoto) elenco interno di tutti i valori degli argomenti forniti dopo thisArg (arg1, arg2 ecc.), Nell'ordine.
  4. Sia F un nuovo oggetto nativo ECMAScript.
  5. Impostare tutti i metodi interni, ad eccezione di [[Ottieni]], di F come specificato in 8.12.
  6. Imposta la proprietà interna [[Ottieni]] di F come specificato in 15.3.5.4.
  7. Impostare la proprietà interna [[TargetFunction]] di F su Target.
  8. Impostare la proprietà interna [[BoundThis]] di F sul valore di thisArg.
  9. Impostare la proprietà interna [[BoundArgs]] di F su A.
  10. Impostare la proprietà interna [[Classe]] di F su "Funzione".
  11. Impostare la proprietà interna [[Prototipo]] di F sull'oggetto prototipo di funzione built-in standard come specificato in 15.3.3.1.
  12. Impostare la proprietà interna [[Call]] di F come descritto in 15.3.4.5.1.
  13. Imposta la proprietà interna [[Costrutto]] di F come descritto in 15.3.4.5.2.
  14. Impostare la proprietà interna [[HasInstance]] di F come descritto in 15.3.4.5.3.
  15. Se la proprietà interna [[Classe]] di Destinazione è "Funzione", quindi a. Sia L la proprietà length di Target meno la lunghezza di A. b. Imposta la proprietà di lunghezza di F su 0 o L, a seconda di quale sia più grande.
  16. Altrimenti impostare la proprietà di lunghezza su F su 0.
  17. Impostare gli attributi della proprietà di lunghezza F su valori specificati in 15.3.5.1.
  18. Imposta la proprietà interna [[Extensible]] di F su true.
  19. Lascia che il lanciatore sia l'oggetto [[ThrowTypeError]] Object (13.2.3).
  20. Chiama il metodo interno [[DefineOwnProperty]] di F con gli argomenti "chiamante", PropertyDescriptor {[[Get]]: lanciatore, [[Set]]: lanciatore, [[Enumerable]]: false, [[Configurabile ]]: falso} e falso.
  21. Chiama il metodo interno [[DefineOwnProperty]] di F con argomenti "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable ]]: falso} e falso.
  22. ritorno F

E quando si chiama un function sul vostro oggetto che è stato creato con bind:

15.3.4.5.1 [[CALL]]

Quando il [[Call]] metodo interno di un oggetto funzione, F, che è stato creato utilizzando la funzione bind è chiamato con un valore e un elenco di argomenti ExtraArgs, i passaggi seguenti sono preso:

  1. Lascia che boundArgs sia il valore della proprietà interna di [[BoundArgs]] di F.
  2. Let bound: questo è il valore della proprietà interna di [[BoundThis]] di F.
  3. Lascia che il target sia il valore della proprietà interna di [[TargetFunction]] di F.
  4. Consentire ad args di essere una nuova lista contenente gli stessi valori dell'elenco boundArgs nello stesso ordine seguito dagli stessi valori dell'elenco ExtraArgs nello stesso ordine.
  5. Restituisce il risultato della chiamata al [[CALL]] metodo interno di destinazione fornendo boundThis come questo valore e forniscono argomenti come le argomenti

chiamata specifica come ogni funzione viene chiamata . E un po 'assomiglia al JavaScript call:

someFunction.[[call]](thisValue, arguments) { 

} 

Tuttavia quando [[call]] viene utilizzato su una funzione legata, il thisValue viene sovrascritto con il valore di [[BoundThis]]. Nel caso di chiamare bind una seconda volta, il thisValue che si tenta di ignorare il primo con è sostituito da [[BoundThis]], essenzialmente incorrere in alcun effetto sul valore del thisValue:

boundFunction.[[call]](thisValue, arguments) { 
    thisValue = boundFunction.[[BoundThis]]; 
} 

Si noterà che se si prova a utilizzare call o apply quindi non avranno alcun effetto perché il loro tentativo di ignorare la proprietà thisValue verrà annullato quando [[call]] invoca la funzione successiva.

5

di torazaburo mi ha dato un'idea. Sarebbe possibile per una funzione di tipo bind, invece di cuocere il ricevitore (questo) nella chiamata all'interno di una chiusura, per inserirlo come una proprietà sull'oggetto funzione e quindi utilizzarlo quando viene effettuata la chiamata. Ciò consentirebbe a un rebind di aggiornare la proprietà prima che venga effettuata la chiamata, dando effettivamente i risultati di rebind che ci si aspettava.

Ad esempio,

function original_fn() { 
 
    document.writeln(JSON.stringify(this)); 
 
} 
 

 
Function.prototype.rebind = function(obj) { 
 
    var fn = this; 
 
    var bound = function func() { 
 
     fn.call(func.receiver, arguments); 
 
    }; 
 
    bound.receiver = obj; 
 
    bound.rebind = function(obj) { 
 
     this.receiver = obj; 
 
     return this; 
 
    }; 
 
    return bound; 
 
} 
 

 
var bound_fn = original_fn.rebind({foo: 'bar'}); 
 

 
bound_fn(); 
 

 
var rebound_fn = bound_fn.rebind({fred: 'barney'}); 
 

 
rebound_fn();

Oppure, l'uscita dal node.js è il seguente.

{ foo: 'bar' } 
{ fred: 'barney' } 

Si noti che la prima chiamata a rebind chiama quello che è stato aggiunto al Function.prototype dal momento che viene chiamato funzione ordinaria original_fn, ma la seconda chiamata viene chiamata la rebind che è stato aggiunto come una proprietà per il limite funzione (e ogni chiamata successiva chiamerà anche questa). Che rebind aggiorni semplicemente receiver e restituisca lo stesso oggetto funzione.

È possibile accedere alla proprietà receiver all'interno della funzione associata rendendola un'espressione di funzione denominata .

+1

Nei frammenti di codice puoi usare 'document.writeln'. –

+0

Molto creativo, +1. –

+0

@torazaburo Sfortunatamente, 'document.writeln' non ha la capacità di formattare correttamente gli oggetti. Sostituire 'console.log' con' document.writeln' produce appena '[Oggetto oggetto]' come output. –

0

Questi esempi semplificati di come funziona lo bind() lo spiegano meglio.

Ecco cosa funzione legata una volta si presenta come:

function bound_function() { 

    function original_function() { 
     console.log(self); 
    } 

    var self = 1; 
    original_function(); 
} 

bound_function() 

Ecco cosa succede se ci avvolgiamo funzione originale due volte:

function bound_function2() { 

    function bound_function1() { 

     function original_function() { 
      console.log(self); 
     } 

     var self = 1; 
     original_function(); 
    } 

    var self = 2; 
    bound_function1(); 
} 

bound_function2() 
0

Penso che il modo di pensare è: quando si call bind() la prima volta che il valore di "this" all'interno della funzione restituita dalla chiamata a bind() è FIXED, al valore dato. Questo è possibile PERCHÉ non è stato corretto prima, non era legato. Ma una volta riparato, non può essere riparato da altro perché non è più non fissato, non è più una "variabile".

In teoria ci potrebbe essere un'operazione opposta di legarsi chiamato "unbind", che si potrebbe chiamare come:

myFunk.bind(something) 
     .unbind(); // -> has same behavior as original myFunk 

Il nome "legare" indica che la (pseudo) variabile 'questo' è destinata a qualcosa, non è semplicemente ASSEGNATO un valore, che potrebbe quindi essere assegnato ancora e ancora.

Quando qualcosa è "vincolato" ha un valore e tale valore non può essere sostituito, perché è "vincolato". Quindi avresti bisogno di un'operazione unbind() per renderlo possibile. Ma dal momento che presumibilmente hai la funzione originale attorno a qualche punto, , non c'è davvero bisogno di "unbind".

Sono d'accordo che questo comportamento è forse sorprendente e inaspettato e quindi probabilmente soggetto a errori perché se si ottiene una funzione come argomento non sembra esserci alcun modo per stabilire se il bind() su di esso ha o meno un effetto.

TUTTAVIA se non si conosce molto su tale argomento di funzione sarebbe anche impossibile sapere quale tipo di valore si può associare ad esso senza interrompere le chiamate che esso fa a "questo" al suo interno.

SO l'operazione Bind() stessa è piuttosto pericolosa. La rilegatura sarebbe doppiamente pericolosa. Quindi stai meglio cercando di evitare di farlo se possibile.