Оновлення Chrome 59: Як я передбачав у відповіді нижче, прив’язка більше не працює повільніше за допомогою нового компілятора оптимізації. Ось код із деталями: https://codereview.chromium.org/2916063002/
Найчастіше це не має значення.
Якщо ви не створюєте додаток, де .bind
є вузьке місце, я б не турбував. У більшості випадків читабельність набагато важливіша за чисту продуктивність. Я думаю, що використання рідної мови, .bind
як правило, забезпечує більш читабельний та ремонтопридатний код - що є великим плюсом.
Однак так, коли це важливо - .bind
повільніше
Так, .bind
це значно повільніше, ніж закриття - принаймні в Chrome, принаймні в поточному способі, в якому це реалізовано v8
. Мені особисто доводилося кілька разів переключатись на Node.JS для вирішення проблем із продуктивністю (загальніше, закриття в певних ситуаціях інтенсивне).
Чому? Оскільки .bind
алгоритм набагато складніший, ніж обтікання функції іншою функцією та використання.call
або.apply
. (Цікаво, що він також повертає функцію з toString, встановлену на [рідна функція]).
Існує два способи розглянути це, з точки зору специфікації та з точки зору реалізації. Давайте спостерігатимемо обидва.
- Нехай Target - це значення.
- Якщо IsCallable (Target) хибне, викиньте виняток TypeError.
- Нехай A - це новий (можливо, порожній) внутрішній список усіх значень аргументів, наданих після thisArg (arg1, arg2 тощо), по порядку.
...
(21. Викличте [[DefineOwnProperty]] внутрішній метод F з аргументами «аргументи», PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable] ]: false} і false.
(22. Повернення Ф.
Виглядає досить складно, набагато більше, ніж просто обгортання.
Давайте перевіримо FunctionBind
вихідний код v8 (chrome JavaScript engine):
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;
Тут ми можемо побачити купу дорогих речей у реалізації. А саме %_IsConstructCall()
. Це, звичайно, потрібно для дотримання специфікації, але це також робить це повільнішим, ніж просте обгортання у багатьох випадках.
З іншого боку, виклики .bind
також дещо відрізняються: специфікації "Об'єкти функцій, створені за допомогою Function.prototype.bind, не мають властивості прототипу або внутрішніх [[Код]], [[ФормальніПараметри]] та [[Сфера]] властивості "