Poprzedni plakat pytał Function.bind vs Closure in Javascript: jak wybrać?
i otrzymałem tę odpowiedź częściowo, która wydaje się wskazywać, że wiązanie powinno być szybsze niż zamknięcie:
Przechodzenie przez zakres oznacza, że gdy dochodzi się do pobrania wartości (zmiennej, obiektu), która istnieje w innym zakresie, w związku z tym dodawany jest dodatkowy narzut (wykonanie kodu staje się wolniejsze).
Używając bind, wywołujesz funkcję z istniejącym zakresem, więc przechodzenie między zasięgiem nie ma miejsca.
Dwa jsperf sugerują, że bind jest w rzeczywistości dużo, dużo wolniejszy niż zamknięcie .
Zostało to opublikowane jako komentarz do powyższego
Postanowiłem napisać własny plik jsperf
Dlaczego więc wiąże się o wiele wolniej (70 +% na chromie)?
Skoro nie jest to szybsze, a zamknięcia mogą służyć temu samemu celowi, czy należy unikać wiązania?
źródło
apply/call/bind
) są generalnie znacznie wolniejsze niż bezpośrednie.Odpowiedzi:
Aktualizacja Chrome 59: Jak przewidziałem w odpowiedzi poniżej, bind nie jest już wolniejszy z nowym optymalizującym kompilatorem. Oto kod ze szczegółami: https://codereview.chromium.org/2916063002/
W większości przypadków nie ma to znaczenia.
Chyba że tworzysz aplikację, w której
.bind
jest wąskie gardło, nie zawracałbym sobie głowy. W większości przypadków czytelność jest znacznie ważniejsza niż sama wydajność. Myślę, że użycie natywnego.bind
zwykle zapewnia bardziej czytelny i łatwiejszy w utrzymaniu kod - co jest dużym plusem.Jednak tak, kiedy ma to znaczenie -
.bind
jest wolniejszeTak,
.bind
jest znacznie wolniejsze niż zamknięcie - przynajmniej w Chrome, przynajmniej w obecnym sposobie implementacjiv8
. Osobiście musiałem czasami przełączać się w Node.JS z powodu problemów z wydajnością (ogólnie rzecz biorąc, zamknięcia są trochę powolne w sytuacjach wymagających dużej wydajności).Czemu? Ponieważ
.bind
algorytm jest o wiele bardziej skomplikowany niż zawijanie funkcji inną funkcją i użycie.call
lub.apply
. (Ciekawostka, zwraca także funkcję z parametrem toString ustawionym na [funkcja natywna]).Można na to spojrzeć na dwa sposoby, z punktu widzenia specyfikacji oraz z punktu widzenia implementacji. Przyjrzyjmy się obu.
Najpierw przyjrzyjmy się algorytmowi wiązania zdefiniowanemu w specyfikacji :
Wydaje się dość skomplikowane, znacznie więcej niż tylko chusta.
Po drugie, zobaczmy, jak jest zaimplementowany w Chrome .
Sprawdźmy w
FunctionBind
kodzie źródłowym v8 (silnik Chrome JavaScript):function FunctionBind(this_arg) { // Length is 1. if (!IS_SPEC_FUNCTION(this)) { throw new $TypeError('Bind must be called on a function'); } var boundFunction = function () { // Poison .arguments and .caller, but is otherwise not detectable. "use strict"; // This function must not use any object literals (Object, Array, RegExp), // since the literals-array is being used to store the bound data. if (%_IsConstructCall()) { return %NewObjectFromBound(boundFunction); } var bindings = %BoundFunctionGetBindings(boundFunction); var argc = %_ArgumentsLength(); if (argc == 0) { return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2); } if (bindings.length === 2) { return %Apply(bindings[0], bindings[1], arguments, 0, argc); } var bound_argc = bindings.length - 2; var argv = new InternalArray(bound_argc + argc); for (var i = 0; i < bound_argc; i++) { argv[i] = bindings[i + 2]; } for (var j = 0; j < argc; j++) { argv[i++] = %_Arguments(j); } return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc); }; %FunctionRemovePrototype(boundFunction); var new_length = 0; if (%_ClassOf(this) == "Function") { // Function or FunctionProxy. var old_length = this.length; // FunctionProxies might provide a non-UInt32 value. If so, ignore it. if ((typeof old_length === "number") && ((old_length >>> 0) === old_length)) { var argc = %_ArgumentsLength(); if (argc > 0) argc--; // Don't count the thisArg as parameter. new_length = old_length - argc; if (new_length < 0) new_length = 0; } } // This runtime function finds any remaining arguments on the stack, // so we don't pass the arguments object. var result = %FunctionBindArguments(boundFunction, this, this_arg, new_length); // We already have caller and arguments properties on functions, // which are non-configurable. It therefore makes no sence to // try to redefine these as defined by the spec. The spec says // that bind should make these throw a TypeError if get or set // is called and make them non-enumerable and non-configurable. // To be consistent with our normal functions we leave this as it is. // TODO(lrn): Do set these to be thrower. return result;
W implementacji widzimy wiele drogich rzeczy. Mianowicie
%_IsConstructCall()
. Jest to oczywiście konieczne, aby zachować zgodność ze specyfikacją - ale w wielu przypadkach powoduje również, że jest wolniejsze niż zwykłe zawijanie.Z drugiej strony, wywołanie
.bind
jest również nieco inne, uwagi specyfikacji „Obiekty funkcji utworzone za pomocą funkcji Function.prototype.bind nie mają właściwości prototypu ani wewnętrznych właściwości [[Code]], [[FormalParameters]] i [[Scope]] nieruchomości"źródło
.bind
w przeglądarce, czytelny i zrozumiały kod jest o wiele ważniejszy w większości przypadków. Jeśli chodzi o szybkość funkcji związanych - tak, funkcje ograniczone pozostaną w tej chwili wolniejsze , zwłaszcza gdythis
wartość nie jest używana w częściowej. Możesz to zobaczyć na podstawie testu porównawczego, specyfikacji i / lub niezależnie od wdrożenia (benchmark) .Chcę tu tylko trochę spojrzeć:
Zauważ, że podczas gdy
bind()
ing jest powolne, wywoływanie funkcji raz związanych już nie!Mój kod testowy w przeglądarce Firefox 76.0 w systemie Linux:
//Set it up. q = function(r, s) { }; r = {}; s = {}; a = []; for (let n = 0; n < 1000000; ++n) { //Tried all 3 of these. //a.push(q); //a.push(q.bind(r)); a.push(q.bind(r, s)); } //Performance-testing. s = performance.now(); for (let x of a) { x(); } e = performance.now(); document.body.innerHTML = (e - s);
Więc chociaż prawdą jest, że
.bind()
ing może być około ~ 2X wolniejsze niż niewiążące (testowałem to również), powyższy kod zajmuje taką samą ilość czasu dla wszystkich 3 przypadków (wiązanie 0, 1 lub 2 zmiennych).Osobiście nie obchodzi mnie, czy
.bind()
ingowanie jest powolne w moim obecnym przypadku użycia, zależy mi na wydajności wywoływanego kodu, gdy te zmienne są już powiązane z funkcjami.źródło