In einem früheren Poster wurde Function.bind vs Closure in Javascript gefragt : Wie soll man wählen?
und erhielt diese Antwort teilweise, was darauf hindeutet, dass die Bindung schneller sein sollte als ein Abschluss:
Bereichsdurchquerung bedeutet, dass beim Erreichen eines Werts (Variable, Objekt), der in einem anderen Bereich vorhanden ist, zusätzlicher Overhead hinzugefügt wird (die Ausführung von Code wird langsamer).
Mit bind rufen Sie eine Funktion mit einem vorhandenen Bereich auf, sodass keine Bereichsüberquerung stattfindet.
Zwei jsperfs legen nahe, dass die Bindung tatsächlich viel, viel langsamer ist als ein Abschluss .
Dies wurde als Kommentar zu den oben genannten gepostet
Und ich beschloss, mein eigenes jsperf zu schreiben
Warum ist die Bindung so viel langsamer (70 +% auf Chrom)?
Sollte eine Bindung vermieden werden, da sie nicht schneller ist und Verschlüsse denselben Zweck erfüllen können?
quelle
apply/call/bind
) sind im Allgemeinen viel langsamer als direkte.Antworten:
Chrome 59-Update: Wie ich in der folgenden Antwort vorausgesagt habe, ist die Bindung mit dem neuen optimierenden Compiler nicht mehr langsamer. Hier ist der Code mit Details: https://codereview.chromium.org/2916063002/
Meistens spielt es keine Rolle.
Wenn Sie keine Anwendung erstellen, in
.bind
der der Engpass liegt, würde ich mich nicht darum kümmern. Die Lesbarkeit ist in den meisten Fällen viel wichtiger als die reine Leistung. Ich denke, dass die Verwendung von Native.bind
normalerweise besser lesbaren und wartbaren Code bietet - was ein großes Plus ist.Aber ja, wenn es darauf ankommt -
.bind
ist langsamerJa,
.bind
ist erheblich langsamer als ein Abschluss - zumindest in Chrome, zumindest in der aktuellen Implementierungv8
. Ich persönlich musste Node.JS manchmal wegen Leistungsproblemen einschalten (im Allgemeinen sind Schließungen in leistungsintensiven Situationen etwas langsam).Warum? Weil der
.bind
Algorithmus viel komplizierter ist als das Umschließen einer Funktion mit einer anderen Funktion und das Verwenden von.call
oder.apply
. (Unterhaltsame Tatsache, es gibt auch eine Funktion zurück, bei der toString auf [native Funktion] gesetzt ist).Es gibt zwei Möglichkeiten, dies unter dem Gesichtspunkt der Spezifikation und unter dem Gesichtspunkt der Implementierung zu betrachten. Beobachten wir beide.
Schauen wir uns zunächst den in der Spezifikation definierten Bindungsalgorithmus an :
Scheint ziemlich kompliziert, viel mehr als nur ein Wrap.
Zweitens wollen wir sehen, wie es in Chrome implementiert ist .
Lassen Sie uns
FunctionBind
den Quellcode der Version 8 (Chrome JavaScript Engine) einchecken: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;
Wir können hier in der Implementierung eine Reihe teurer Dinge sehen. Nämlich
%_IsConstructCall()
. Dies ist natürlich erforderlich, um die Spezifikation einzuhalten - aber es macht es in vielen Fällen auch langsamer als eine einfache Umhüllung.In einem anderen Hinweis unterscheidet sich der Aufruf
.bind
ebenfalls geringfügig. Die Spezifikationshinweise "Mit Function.prototype.bind erstellte Funktionsobjekte haben keine Prototyp-Eigenschaft oder die internen [[Code]], [[FormalParameters]] und [[Scope]] Eigenschaften"quelle
.bind
im Browser vermieden , lesbarer und verständlicher Code ist in den meisten Fällen viel wichtiger. Bezüglich der Geschwindigkeit gebundener Funktionen - Ja, gebundene Funktionen bleiben im Moment langsamer , insbesondere wenn derthis
Wert im Teil nicht verwendet wird. Sie sehen dies am Benchmark, an der Spezifikation und / oder an der Implementierung unabhängig (Benchmark) .Ich möchte hier nur ein wenig Perspektive geben:
Beachten Sie, dass während
bind()
ing langsam ist, ruft die Funktionen einmal gebunden ist , nicht!Mein Testcode in Firefox 76.0 unter 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);
Während es wahr ist, dass
.bind()
ing etwas ~ 2X langsamer sein kann als nicht zu binden (das habe ich auch getestet), benötigt der obige Code für alle 3 Fälle die gleiche Zeit (Bindung von 0, 1 oder 2 Variablen).Persönlich ist es mir egal, ob der
.bind()
Ing in meinem aktuellen Anwendungsfall langsam ist. Ich kümmere mich um die Leistung des aufgerufenen Codes, sobald diese Variablen bereits an die Funktionen gebunden sind.quelle