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 wolniejsze
Tak, .bind
jest znacznie wolniejsze niż zamknięcie - przynajmniej w Chrome, przynajmniej w obecnym sposobie implementacji v8
. 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.
- Niech Target będzie tą wartością.
- Jeśli IsCallable (Target) ma wartość false, zgłoś wyjątek TypeError.
- Niech A będzie nową (prawdopodobnie pustą) wewnętrzną listą wszystkich wartości argumentów podanych po thisArg (arg1, arg2 itd.), W kolejności.
...
(21. Wywołaj wewnętrzną metodę [[DefineOwnProperty]] F z argumentami „arguments”, PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable] ]: false} i false.
(22. Powrót F.
Wydaje się dość skomplikowane, znacznie więcej niż tylko chusta.
Sprawdźmy w FunctionBind
kodzie źródłowym v8 (silnik Chrome JavaScript):
function FunctionBind(this_arg) {
if (!IS_SPEC_FUNCTION(this)) {
throw new $TypeError('Bind must be called on a function');
}
var boundFunction = function () {
"use strict";
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") {
var old_length = this.length;
if ((typeof old_length === "number") &&
((old_length >>> 0) === old_length)) {
var argc = %_ArgumentsLength();
if (argc > 0) argc--;
new_length = old_length - argc;
if (new_length < 0) new_length = 0;
}
}
var result = %FunctionBindArguments(boundFunction, this,
this_arg, new_length);
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"