Используя Function.prototype.bind с массивом аргументов?

javascript functional-programming promise partial-application

13693 просмотра

10 ответа

22352 Репутация автора

Как я могу вызвать Function.prototype.bind с массивом аргументов, в отличие от жестко закодированных аргументов? (Не использует ECMA6, поэтому нет оператора спреда).

Я пытаюсь поместить оболочку обещаний вокруг модуля, который использует обратные вызовы, и я хочу связать все аргументы, переданные моему методу оболочки, и связать их. Затем я хочу вызвать частично примененную связанную функцию с моим собственным обратным вызовом, который разрешит или отклонит обещание.

var find = function() {
  var deferred, bound;
  deferred = Q.defer();
  bound = db.find.bind(null, arguments);
  bound(function(err, docs) {
    if(err) {
      deferred.fail(err);
    } else {
      deferred.resolve(docs);
    }
  });
  return deferred.promise;
}

Но очевидно, что это не работает, потому что bind ожидает аргументы, а не массив аргументов. Я знаю, что мог бы сделать это, вставив мой обратный вызов в конец массива arguments и используя apply:

arguments[arguments.length] = function(err, docs) { ... }
db.find.apply(null, arguments);

Или путем перебора массива arguments и повторного связывания функции для каждого аргумента:

var bound, context;
for(var i = 0; i < arguments.length; i++) {
   context = bound ? bound : db.find;
   bound = context.bind(null, arguments[i]);
}
bound(function(err, docs) { ... })

Но оба эти метода чувствуют себя грязными. Есть идеи?

Автор: Dan Prince Источник Размещён: 02.02.2014 05:13

Ответы (10)


1 плюс

24564 Репутация автора

Почему бы просто не выполнить привязку к массиву arguments в соответствии с вашим примером и заставить bound()функцию обрабатывать его так же, как массив ?

Судя по тому, как вы используете, вы передаете функцию в качестве последнего аргумента bound(), то есть, передавая массив фактических аргументов, вы избегаете необходимости отделять аргументы от обратных вызовов внутри bound(), что потенциально облегчает игру.

Автор: Matt Way Размещён: 02.02.2014 05:35

78 плюса

599640 Репутация автора

Решение

.bindэто нормальная функция, так что вы можете вызвать .applyее.
Все, что вам нужно сделать, это передать исходную функцию в качестве первого параметра и желаемую THISпеременную в качестве первого элемента в массиве аргументов:

bound = db.find.bind.apply(db.find, [null].concat(arguments));
//      ^-----^            ^-----^   THIS

Будет ли это считаться чище или нет, остается за читателем.

Автор: Felix Kling Размещён: 02.02.2014 05:39

0 плюса

22352 Репутация автора

Просто была альтернативная идея, частично применить nullзначение для контекста, а затем использовать applyдля вызова частично примененной функции.

bound = db.find.bind.bind(null).apply(null, arguments);

Это устраняет необходимость в слегка пугающем взгляде [null].concat()на ответ @ Феликса.

Автор: Dan Prince Размещён: 15.02.2014 02:03

11 плюса

53907 Репутация автора

Ниже приведен общий фрагмент кода, который я использую во всех своих проектах:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

Теперь bindableфункцию можно использовать для передачи массива bindследующим образом:

var bound = bindable(db.find, db).apply(null, arguments);

На самом деле вы можете кэшировать bindable(db.find, db)для ускорения привязки следующим образом:

var findable = bindable(db.find, db);
var bound = findable.apply(null, arguments);

Вы можете использовать findableфункцию с массивом аргументов или без него:

var bound = findable(1, 2, 3);

Надеюсь это поможет.

Автор: Aadit M Shah Размещён: 15.02.2014 03:13

9 плюса

1668 Репутация автора

Ответ Феликса не сработал для меня, потому что argumentsобъект на самом деле не является массивом (как отметил Оттс). Решением для меня было просто переключиться bindи apply:

bound = db.find.apply.bind(db.find, null, arguments);
Автор: Joel Размещён: 06.06.2014 04:50

1 плюс

16940 Репутация автора

Обычно этой схемы достаточно:

//obj = db
//fnName = 'find'
var args = [this||null].concat(Array.prototype.slice.apply(arguments);
obj[fnName].bind.apply(obj[fnName], args);
Автор: Lorenz Lo Sauer Размещён: 05.02.2015 12:17

1 плюс

6597 Репутация автора

Я нахожу следующее чище, чем принятые ответы

Function.bind.apply(db.find, [null].concat(arguments));
Автор: brillout Размещён: 17.11.2016 12:19

0 плюса

2034 Репутация автора

Окончательный и простой ответ может быть

Function.apply.bind(this.method, this, arguments);

Вроде «трудно» понять, но аккуратно.

Автор: 131 Размещён: 25.11.2016 09:25

4 плюса

6759 Репутация автора

Для тех, кто использует ES6, Babel компилирует:

db.find.bind(this, ...arguments)

чтобы:

db.find.bind.apply(db.find, [this].concat(Array.prototype.slice.call(arguments)));

Я думаю, будет справедливо сказать, что Бабель довольно определенна. Кредит @ lorenz-lo-sauer, хотя, это почти идентично.

Автор: nathancahill Размещён: 30.04.2017 05:48

1 плюс

1906 Репутация автора

Если кто-то ищет абстрактный образец:

var binded = hello.apply.bind(hello,null,['hello','world']);

binded();

function hello(a,b){
  console.log(this); //null
  console.log(a); //hello
  console.log(b); //world
}
Автор: Paweł Размещён: 14.08.2017 09:27
Вопросы из категории :
32x32