Почему bind медленнее, чем закрытие?
предыдущий плакат попросил
1 ответ:
обновление Chrome 59: Как я и предсказывал в ответе ниже bind больше не медленнее с новым оптимизирующим компилятором. Вот код с подробностями:https://codereview.chromium.org/2916063002/
большую часть времени это не имеет значения.
если вы не создаете приложение, где
.bind
это узкое место, которое я бы не беспокоил. Читаемость гораздо важнее, чем производительность в большинстве случаев. Я думаю, что с помощью родной.bind
обычно обеспечивает более читаемый и поддерживаемый код-что является большим плюсом.однако Да, когда это имеет значение -
.bind
медленнееда
.bind
значительно медленнее, чем закрытие - по крайней мере, в Chrome, по крайней мере, в текущем виде он реализован вv8
. Мне лично пришлось переключиться в узел.JS для проблем с производительностью несколько раз (в более общем случае закрытие происходит медленно в ситуациях с интенсивной производительностью).почему? Потому что
.bind
алгоритм намного сложнее, чем обертывание функции другой функцией и использование.call
или.apply
. (Забавный факт, он также возвращает функцию с toString, установленным в [native function]).есть два способа смотреть на это, с точки зрения спецификации, и с точки зрения реализации. Давайте понаблюдаем за обоими.
сначала посмотрите на алгоритм привязки, определенный в спецификации:
- пусть Target-это значение.
- если IsCallable (Target) имеет значение false, создайте исключение TypeError.
- пусть 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 не имеет свойства прототипа или [[код]], [[формальные параметры]] и [[область]] внутренних свойств"