Un cartel anterior preguntaba Function.bind vs Closure en Javascript: ¿cómo elegir?
y recibió esta respuesta en parte, lo que parece indicar que el enlace debería ser más rápido que un cierre:
El traspaso del alcance significa, cuando está tratando de tomar un valor (variable, objeto) que existe en un alcance diferente, por lo tanto, se agrega una sobrecarga adicional (el código se vuelve más lento de ejecutar).
Al usar bind, está llamando a una función con un alcance existente, por lo que no se realiza el recorrido del alcance.
Dos jsperfs sugieren que bind es en realidad mucho, mucho más lento que un cierre .
Esto fue publicado como un comentario a lo anterior.
Y decidí escribir mi propio jsperf
Entonces, ¿por qué la unión es mucho más lenta (más del 70% en cromo)?
Dado que no es más rápido y los cierres pueden servir para el mismo propósito, ¿debería evitarse la unión?
fuente
apply/call/bind
) son en general mucho más lentas que las directas.Respuestas:
Actualización de Chrome 59: como predije en la respuesta a continuación, el enlace ya no es más lento con el nuevo compilador de optimización. Aquí está el código con detalles: https://codereview.chromium.org/2916063002/
La mayoría de las veces no importa.
A menos que esté creando una aplicación donde
.bind
está el cuello de botella, no me molestaría. La legibilidad es mucho más importante que el puro rendimiento en la mayoría de los casos. Creo que el uso de nativo.bind
generalmente proporciona un código más legible y fácil de mantener, lo cual es una gran ventaja.Sin embargo, sí, cuando importa,
.bind
es más lentoSí,
.bind
es considerablemente más lento que un cierre, al menos en Chrome, al menos en la forma actual en que se implementav8
. Personalmente, en ocasiones tuve que cambiar Node.JS por problemas de rendimiento (de manera más general, los cierres son algo lentos en situaciones de rendimiento intensivo).¿Por qué? Porque el
.bind
algoritmo es mucho más complicado que envolver una función con otra función y usar.call
o.apply
. (Dato curioso, también devuelve una función con toString establecido en [función nativa]).Hay dos formas de ver esto, desde el punto de vista de la especificación y desde el punto de vista de la implementación. Observemos ambos.
Primero, veamos el algoritmo de vinculación definido en la especificación :
Parece bastante complicado, mucho más que una simple envoltura.
En segundo lugar, veamos cómo se implementa en Chrome .
Revisemos el
FunctionBind
código fuente v8 (motor JavaScript de Chrome):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;
Podemos ver un montón de cosas caras aquí en la implementación. Es decir
%_IsConstructCall()
. Por supuesto, esto es necesario para cumplir con la especificación, pero también lo hace más lento que un simple ajuste en muchos casos.En otra nota, la llamada
.bind
también es ligeramente diferente, las notas de especificación "Los objetos de función creados con Function.prototype.bind no tienen una propiedad de prototipo o el [[Code]], [[FormalParameters]] y [[Scope]] internos propiedades "fuente
.bind
en el navegador, el código legible y comprensible es mucho más importante en la mayoría de los casos. En cuanto a la velocidad de las funciones vinculadas, sí, las funciones vinculadas permanecerán más lentas en este momento , especialmente cuando elthis
valor no se usa en el parcial. Puede ver esto desde el punto de referencia, desde la especificación y / o desde la implementación de forma independiente (punto de referencia) .Solo quiero dar un poco de perspectiva aquí:
Tenga en cuenta que mientras
bind()
ing es lento, llamar a las funciones una vez enlazadas no lo es.Mi código de prueba en Firefox 76.0 en 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);
Entonces, si bien es cierto que
.bind()
ing puede ser ~ 2 veces más lento que no vinculante (también lo probé), el código anterior toma la misma cantidad de tiempo para los 3 casos (vinculando 0, 1 o 2 variables).Personalmente, no me importa si el
.bind()
ing es lento en mi caso de uso actual, me importa el rendimiento del código que se llama una vez que esas variables ya están vinculadas a las funciones.fuente