Почему bind медленнее, чем закрытие?


предыдущий плакат попросил

1 72

1 ответ:

обновление Chrome 59: Как я и предсказывал в ответе ниже bind больше не медленнее с новым оптимизирующим компилятором. Вот код с подробностями:https://codereview.chromium.org/2916063002/

большую часть времени это не имеет значения.

если вы не создаете приложение, где .bind это узкое место, которое я бы не беспокоил. Читаемость гораздо важнее, чем производительность в большинстве случаев. Я думаю, что с помощью родной .bind обычно обеспечивает более читаемый и поддерживаемый код-что является большим плюсом.

однако Да, когда это имеет значение - .bind медленнее

да .bind значительно медленнее, чем закрытие - по крайней мере, в Chrome, по крайней мере, в текущем виде он реализован в v8. Мне лично пришлось переключиться в узел.JS для проблем с производительностью несколько раз (в более общем случае закрытие происходит медленно в ситуациях с интенсивной производительностью).

почему? Потому что .bind алгоритм намного сложнее, чем обертывание функции другой функцией и использование .call или .apply. (Забавный факт, он также возвращает функцию с toString, установленным в [native function]).

есть два способа смотреть на это, с точки зрения спецификации, и с точки зрения реализации. Давайте понаблюдаем за обоими.

сначала посмотрите на алгоритм привязки, определенный в спецификации:

  1. пусть Target-это значение.
  2. если IsCallable (Target) имеет значение false, создайте исключение TypeError.
  3. пусть A-новый (возможно пустой) внутренний список всех значений аргументов, предоставленных после thisArg (arg1, arg2 и т. д.), по порядку.

...

(21. Вызовите [[DefineOwnProperty]] внутренний метод F с аргументами "аргументы", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Настраивается]]: false} и false.

(22. Возвращение Ф.

кажется довольно сложным, намного больше, чем просто обернуть.

во-вторых , давайте посмотрим как это реализовано в 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 также немного отличается, спецификации отмечает " функциональные объекты, созданные с помощью функции.прототип.bind не имеет свойства прототипа или [[код]], [[формальные параметры]] и [[область]] внутренних свойств"