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 lento
Sí, .bind
es considerablemente más lento que un cierre, al menos en Chrome, al menos en la forma actual en que se implementa v8
. 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.
- Deje que Target sea este valor.
- Si IsCallable (Target) es falso, lanza una excepción TypeError.
- Sea A una nueva lista interna (posiblemente vacía) de todos los valores de argumento proporcionados después de thisArg (arg1, arg2, etc.), en orden.
...
(21. Llame al método interno [[DefineOwnProperty]] de F con argumentos "argumentos", PropertyDescriptor {[[Get]]: lanzador, [[Set]]: lanzador, [[Enumerable]]: falso, [[Configurable] ]: falso} y falso.
(22. Regrese F.
Parece bastante complicado, mucho más que una simple envoltura.
Revisemos el FunctionBind
código fuente v8 (motor JavaScript de Chrome):
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;
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 "