2011-09-02 8 views
22

Mam dość interesujące pytanie dotyczące implementacji EcmaScript-5 Function.prototype.bind. Zazwyczaj podczas korzystania wiążą, robisz to w ten sposób:Function.prototype.bind

var myFunction = function() { 
    alert(this); 
}.bind(123); 

// will alert 123 
myFunction(); 

Dobrze więc, że to fajne, ale to, co jest rzekomo zdarzyć, kiedy to zrobić?

// rebind binded function 
myFunction = myFunction.bind('foobar'); 
// will alert... 123! 
myFunction(); 

Rozumiem, że jest to całkowicie logiczne zachowanie pod względem jak Function.prototype.bind jest realizowany (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind). Ale czy w rzeczywistych warunkach jest to całkowicie bezużyteczne zachowanie, prawda? Pytanie brzmi: czy to błąd czy funkcja? Jeśli to błąd, dlaczego nigdzie go nie wspomniano? Jeśli jest to funkcja, dlaczego Google Chrome z natywną implementacją "bind" zachowuje się w taki sam sposób?

Żeby było jasne, co moim zdaniem byłoby bardziej sensowne, oto fragment kodu, który implementuje Function.prototype.bind trochę inaczej:

if (!Function.prototype.bind) { 
    Function.prototype.bind = function() { 
     var funcObj = this; 
     var original = funcObj; 
     var extraArgs = Array.prototype.slice.call(arguments); 
     var thisObj = extraArgs.shift(); 
     var func = function() { 
      var thatObj = thisObj; 
      return original.apply(thatObj, extraArgs.concat(
       Array.prototype.slice.call(
        arguments, extraArgs.length 
       ) 
      )); 
     }; 
     func.bind = function() { 
      var args = Array.prototype.slice.call(arguments); 
      return Function.prototype.bind.apply(funcObj, args); 
     } 
     return func; 
    }; 
} 

Więc teraz spróbuj to:

// rebind binded function 
myFunction = myFunction.bind('foobar'); 
// will alert... "foobar" 
myFunction(); 

moim zdaniem, zastępując „to” ma większy sens ...

Co myślicie o tym?

+2

To jest funkcja. Gdybyś mógł to zmienić, nie byłby naprawdę "związany", prawda? Szczegóły można znaleźć w specyfikacji: http://ecma262-5.com/ELS5_HTML.htm#Section_15.3.4.5 –

+1

Oczywiście, ale jeśli tak, to jaki jest sposób ustalenia, czy funkcja została już powiązana coś? lub dlaczego nie rzuca żadnego wyjątku podczas próby ponownego powiązania? Chodzi o to, że bardzo trudno jest debugować, jeśli nie wiesz, czy funkcja została powiązana, czy zajmujesz się jej pierwszą kopią. Taka rzecz przytrafiła mi się wczoraj i spędziłem prawie dzień, aby dowiedzieć się, na czym polega problem ... – Ruslan

+1

W każdym razie wygląda na to, że nikt nie odpowie na to pytanie, więc na wypadek, gdyby ktoś inny miał ten sam problem, stworzyłem pasek narzędziowy, który można znaleźć tutaj: http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html – Ruslan

Odpowiedz

14

Precedens dla Function.prototype.bind był implementacją idei w różnych frameworkach JS. Zgodnie z moją najlepszą wiedzą żaden z nich nie pozwolił na zmianę poprzez późniejsze wiązanie. Równie dobrze możesz zapytać, dlaczego żadna z nich nie pozwoliła na to, aby zmiana była wiążąca, i zapytać, dlaczego ES5 na to nie pozwala.

Nie jesteś jedyną osobą, którą słyszałem, która myślała tak dziwnie. Chris Leary, który pracuje nad silnikiem JS Mozilli (tak jak ja), uznał to za nieco dziwne, poruszając problem na Twitterze kilka miesięcy temu. I w nieco innej formie pamiętam jednego z hakerów z Mozilla Labs pytającego, czy istnieje jakiś sposób "odpięcia" funkcji, aby wyodrębnić z niej funkcję celu. (Jeśli mógłbyś to zrobić, możesz oczywiście powiązać go z innym this, przynajmniej jeśli mógłbyś również wyodrębnić listę powiązanych argumentów, aby również przekazać to dalej.)

Nie pamiętam, o czym mowa, gdy Podano bind. Jednak nie zwracałem szczególnej uwagi na listę dyskusyjną es-discuss w czasie, gdy te rzeczy były wymieszane. Powiedział, że nie wierzę, że ES5 szukał innowacji w tym obszarze, po prostu "pave cowpath", pożyczyć frazę.

Być może będziesz w stanie zaproponować introspekcyjne metody rozwiązywania tych problemów w celu omówienia, jeśli napisałeś wystarczająco szczegółową propozycję. Z drugiej strony, wiązanie jest formą mechanizmu ukrywania informacji, który ograniczałby jego przyjęcie. Może warto spróbować coś zaproponować, jeśli masz czas. Domyślam się, że ukrywanie informacji mogłoby zablokować przyjęcie wniosku. Ale to tylko przypuszczenie, które może być błędne. Tylko jeden sposób, aby się dowiedzieć ...

+0

Dziękuję bardzo za odpowiedź, jest bardzo przydatna. Czy mógłbyś opisać procedurę składania takich propozycji? Myślę, że byłoby to przydatne nie tylko w tym przypadku. Z góry dziękuję. – Ruslan

+0

Nie jestem wystarczająco zaangażowany w rzeczywistą pracę normalizacyjną, aby wiedzieć wiele na temat procesu (nie wierzę, że jest w pełni formalna), ale wysyłam pocztę do [es-discuss] (https://mail.mozilla.org/listinfo/es-discuss) sprawi, że piłka będzie się kręciła. Możesz także spróbować zapytać w kanale #jslang na irc.mozilla.org; to jedyne prawdziwe spotkanie, jakie znam na temat standardów językowych JS, które są zaangażowane w określanie rzeczy, więc ktoś może dobrze wiedzieć, jak uzyskać pełną propozycję. –

2

Po związaniu funkcji, pytasz o nową funkcję, która ignoruje swój własny pseudo argument i wywołuje oryginalną funkcję o stałej wartości.

Wiązanie tej funkcji innym razem ma dokładnie to samo zachowanie. Gdyby bind w jakiś sposób załatał w tym nowy, musiałby mieć specjalny przypadek dla funkcji już związanych.

Innymi słowy, bind działa dokładnie tak samo na "normalnych" funkcjach, jak działa na funkcjach zwracanych przez bind, a przy braku nadrzędnych czynników, dobrze jest utrzymywać semantyczną złożoność funkcji na niskim poziomie - to jest łatwiejsze aby zapamiętać, co wiąże się z funkcją, jeśli traktuje wszystkie funkcje wejściowe dokładnie w ten sam sposób, w przeciwieństwie do traktowania niektórych funkcji wejściowych.

Wydaje mi się, że w twoim zamieszaniu widzisz powiązanie modyfikując istniejącą funkcję, a zatem, jeśli zmodyfikujesz go ponownie, spodziewasz się, że oryginalna funkcja zostanie ponownie zmodyfikowana. Jednak bind nie modyfikuje niczego, tworzy nową funkcję z określonym zachowaniem. Nowa funkcja jest sama w sobie funkcją, nie jest to magiczna, łatana wersja oryginalnej funkcji.

Jako takie, nie ma tajemnicy, dlaczego został znormalizowany lub wymyślony w taki sposób, w jaki był: bind zwraca funkcję, która zapewnia nowe i przedkłada argumenty, i działa tak samo we wszystkich funkcjach. Najprostszy możliwy semantyczny.

Tylko w ten sposób jest rzeczywiście bezpieczny w użyciu - jeśli powiązanie spowodowałoby różnicę między już związanymi funkcjami a "normalnymi", należałoby przetestować przed wiązaniem. Dobrym przykładem może być jQuery.each, która przekazuje nowe. Jeśli bind wiązałby się ze specjalnymi funkcjami, nie byłoby bezpiecznego sposobu przekazania powiązanej funkcji do jQuery.each, ponieważ zostanie to nadpisane przy każdym wywołaniu. Aby to naprawić, jQuery.each musiałby jakoś specjalnie przypisać funkcje związane.

+0

Można argumentować, że bind nie powinien w ogóle tego dotykać, ponieważ byłoby to zgodne z innym zachowaniem JS. Jednak powiedziałbym, że najbardziej popularną aplikacją dla bind jest konwersja wywołania metody do wywołania funkcji (javascript nie ma wbudowanych obiektów wywołania metod, które mogłyby zostać przekazane jako callbacki - bind wypełnia ten otwór) –

+0

Co do oczekiwanego zachowania , powinieneś zadać sobie pytanie, dlaczego spodziewasz się powiązania z poprawką w nowej wartości, ale nie wstawiaj nowych argumentów. To dość niekonsekwentne. –

+0

Całkowicie się nie zgadzam! Sposób działania metody bind() jest całkowicie sprzeczny z intuicją. Problem polega na tym, że MUSISZ WIEDZIEĆ, że funkcja była już związana. NIGDY nie możesz być pewien, co otrzymasz z funkcji podczas wywoływania funkcji .bind(). Czasami pojawia się oczekiwane zachowanie.Czasami nie. Jest tak po prostu: związana funkcja ma specjalną semantykę i nie jest po prostu funkcją. Moim zdaniem, związana funkcja powinna być po prostu funkcją, na której można zastosować wszystkie rzeczy z Function.prototype. W przeciwnym razie daj mu inny prototyp, np. BoundFunction i usuń .bind() z niego. – NicBright