Обновление Chrome 59: как я и предсказывал в ответе ниже, привязка больше не работает медленнее с новым оптимизирующим компилятором. Вот код с подробностями: https://codereview.chromium.org/2916063002/
В большинстве случаев это не имеет значения.
Если вы не создаете приложение, в котором .bind
есть узкое место, я бы не стал беспокоиться. В большинстве случаев удобочитаемость гораздо важнее чистой производительности. Я думаю, что использование нативного .bind
кода обычно обеспечивает более читаемый и поддерживаемый код, что является большим плюсом.
Однако да, когда это важно - .bind
медленнее
Да, .bind
это значительно медленнее, чем закрытие - по крайней мере, в Chrome, по крайней мере, в том виде, в котором оно реализовано в настоящее время v8
. Мне лично приходилось несколько раз переключаться на Node.JS из-за проблем с производительностью (в более общем плане, в ситуациях с высокой производительностью закрытие происходит медленно).
Почему? Поскольку .bind
алгоритм намного сложнее, чем обертывание функции другой функцией и использование .call
или .apply
. (Интересный факт, он также возвращает функцию с toString, установленным на [native function]).
Есть два способа взглянуть на это с точки зрения спецификации и с точки зрения реализации. Давайте наблюдать за обоими.
- Пусть Target будет значением this.
- Если IsCallable (Target) имеет значение false, выбросить исключение TypeError.
- Пусть A будет новым (возможно, пустым) внутренним списком всех значений аргументов, указанных после thisArg (arg1, arg2 и т. Д.), По порядку.
...
(21. Вызовите внутренний метод [[DefineOwnProperty]] для F с аргументами «arguments», 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, не имеют свойства прототипа или внутренних [[Code]], [[FormalParameters]] и [[Scope]]» свойства "