Предыдущий постер спрашивал Function.bind vs Closure в Javascript: как выбрать?
и получил этот ответ частично, что, похоже, указывает на то, что привязка должна быть быстрее, чем закрытие:
Обход области видимости означает, что когда вы достигаете, чтобы захватить значение (переменную, объект), которое существует в другой области, поэтому добавляются дополнительные накладные расходы (код становится медленнее для выполнения).
Используя bind, вы вызываете функцию с существующей областью видимости, поэтому обход области видимости не выполняется.
Два jsperfs предполагают, что связывание на самом деле намного медленнее, чем закрытие .
Это было опубликовано как комментарий к приведенному выше
И я решил написать свой собственный jsperf
Так почему же связывание происходит намного медленнее (70 +% хрома)?
Так как это не быстрее и закрытия могут служить той же цели, следует ли избегать связывания?
источник
apply/call/bind
) в целом намного медленнее, чем прямые.Ответы:
Обновление Chrome 59: как я и предсказывал в ответе ниже, привязка больше не работает медленнее с новым оптимизирующим компилятором. Вот код с подробностями: https://codereview.chromium.org/2916063002/
В большинстве случаев это не имеет значения.
Если вы не создаете приложение, в котором
.bind
есть узкое место, я бы не стал беспокоиться. В большинстве случаев удобочитаемость гораздо важнее чистой производительности. Я думаю, что использование нативного.bind
кода обычно обеспечивает более читаемый и поддерживаемый код, что является большим плюсом.Однако да, когда это важно -
.bind
медленнееДа,
.bind
это значительно медленнее, чем закрытие - по крайней мере, в Chrome, по крайней мере, в том виде, в котором оно реализовано в настоящее времяv8
. Мне лично приходилось несколько раз переключаться на Node.JS из-за проблем с производительностью (в более общем плане, в ситуациях с высокой производительностью закрытие происходит медленно).Почему? Поскольку
.bind
алгоритм намного сложнее, чем обертывание функции другой функцией и использование.call
или.apply
. (Интересный факт, он также возвращает функцию с toString, установленным на [native function]).Есть два способа взглянуть на это с точки зрения спецификации и с точки зрения реализации. Давайте наблюдать за обоими.
Во-первых, давайте посмотрим на алгоритм привязки, определенный в спецификации :
Кажется довольно сложным, намного больше, чем просто обертка.
Во-вторых, давайте посмотрим, как это реализовано в Chrome .
Давайте проверим
FunctionBind
исходный код v8 (chrome JavaScript engine):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;
Здесь, в реализации, мы видим кучу дорогих вещей. А именно
%_IsConstructCall()
. Это, конечно, необходимо для соблюдения спецификации, но во многих случаях это также делает его медленнее, чем простой перенос.С другой стороны, вызов
.bind
также немного отличается, примечания к спецификации: «Объекты функций, созданные с помощью Function.prototype.bind, не имеют свойства прототипа или внутренних [[Code]], [[FormalParameters]] и [[Scope]]» свойства "источник
.bind
в браузере, читаемый и понятный код гораздо важнее в большинстве случаев. Что касается скорости связанных функций - да, связанные функции в данный момент будут работать медленнее , особенно когдаthis
значение не используется в партиале. Вы можете увидеть это из теста производительности, из спецификации и / или из реализации независимо (тест) .Я просто хочу показать здесь немного перспективы:
Обратите внимание, что, хотя
bind()
ing работает медленно, вызов функций после привязки - нет!Мой тестовый код в Firefox 76.0 в 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);
Таким образом, хотя это правда, что
.bind()
ing может быть примерно в 2 раза медленнее, чем отсутствие привязки (я тоже это тестировал), приведенный выше код занимает одинаковое количество времени для всех 3 случаев (привязка 0, 1 или 2 переменных).Лично меня не волнует, если
.bind()
в моем текущем варианте использования идет медленный процесс, я забочусь о производительности вызываемого кода, когда эти переменные уже привязаны к функциям.источник