JavaScript部分应用函数-如何只绑定第二个参数?

lmvvr0a8  于 2023-02-02  发布在  Java
关注(0)|答案(5)|浏览(242)

很抱歉我忽略了一些显而易见的东西,但是我不知道如何在javascript中绑定函数的一个特定参数,我学到的大多数函数式编程都是从Scala学来的,所以我不确定这在JS中是否可行。
例如,我知道我可以执行以下操作来绑定第一个参数

var add = function (a, b) {
   return a + b;
};

add(1, 3); //returns 4

var addThree = add.bind(null, 3);  //this = null.  a = 3
addThree(4);                        //returns 7

但是我如何绑定第二个参数而保持第一个不变。换句话说,我如何只绑定到'b'?

从mozilla -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind中我可以看出,参数实际上是嵌套的,这使得它看起来像是必须按照特定的顺序?(我很可能阅读错了)
编辑:我意识到这是一个人为的例子,我只是想学习一下,以防我最终要处理比两个数字相加更复杂的事情,我也想了解bind()参数实际上是如何工作的。

drnojrws

drnojrws1#

当然可以,下面是一个使用spread运算符(...)的ES6解决方案,因为它更紧凑一些。

// Bind arguments starting after however many are passed in.
function bind_trailing_args(fn, ...bound_args) {
    return function(...args) {
        return fn(...args, ...bound_args);
    };
}

如果您希望指定绑定开始的位置:

// Bind arguments starting with argument number "n".
function bind_args_from_n(fn, n, ...bound_args) {
    return function(...args) {
        return fn(...args.slice(0, n-1), ...bound_args);
    };
}

在ES5中,你不得不在构造参数列表上浪费时间。

// ES5 version: construct arguments lists yourself
function bind_trailing_args(fn) {
    var bound_args = [].slice.call(arguments, 1);
    return function() {
        var args = [].concat.call(arguments, bound_args);
        return fn.apply(this, args);
    };
}

与前两个示例不同,这个示例正确地处理了this
在您示例的上下文中:

var addThree = bind_trailing_args(add, 3);
addThree(1) // calls add(1, 3)

您也可以考虑使用JS可用的函数式编程库之一,例如http://osteele.com/sources/javascript/functional/,您需要的东西在那里被称为rcurry

ulmd4ohb

ulmd4ohb2#

可以使用lodash的_.bind来实现这一点:

var add = function(a, b) {
  document.write(a + b);
};

// Bind to first parameter (Nothing special here)
var bound = _.bind(add, null, 3);
bound(4);
// → 7

// Bind to second parameter by skipping the first one with "_"
var bound = _.bind(add, null, _, 4);
bound(3);
// → 7
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.9.3/lodash.min.js"></script>

我通常反对使用库,更喜欢编写自己的实用函数,但是lodash很容易成为一个例外。我强烈建议您在遇到"这一定是该语言中的某个地方!"的时候检查its documentation,它填补了JavaScript中的许多空白。

wlp8pajw

wlp8pajw3#

好吧。我就直说了。

var add = function(a,b) {
  return a + b;
};

var addThree = function(a) {
  return add(a,3);
};

add(1,2);
addThree(4);

也许对一些人来说还可以。

a7qyws3x

a7qyws3x4#

你可以试试这个:

Function.prototype.bindThemAll = function bindThemAll(thisArg, ...boundArgs) 
  (boundArgs.fn = this, function(...args) boundArgs.fn.call(thisArg || this, ...boundArgs.map((el) => el || args.shift()), ...args));

function fn() console.log("fn:", arguments.callee, "this:", this, "args:", arguments)

var x = {a: 5};

var bfn = fn.bindThemAll(x, null, 2)
bfn(1,3)
x.bfn = fn.bindThemAll(null, null, 2)
x.bfn(1,3)

带有null或undefined的绑定参数将被替换为函数参数,剩下的参数将被附加到末尾。如果对象是绑定的,则将使用对象,否则将使用当前的...
查看控制台获取结果!

lnvxswe2

lnvxswe25#

// 'this' arg gets dropped because console.log doesn't log it's own context
test = bindArguments(console.log, 'this', 'one', undefined, 'three', undefined, 'REST-After-This:', Symbol.split, 'END-2', 'END-Argument')

> test('TWO-filled-undefslot')
>> one TWO-filled-undefslot three undefined REST-After-This: END-2 END-Argument

> test(null, undefined, 'rest#1', 'rest#2')
>> one null three undefined REST-After-This: rest#1 rest#2 END-2 END-Argument

> test('TWO-filled-undefslot', 'FOUR-filled-undefslot', 'rest#1', 'rest#2')
>> one 2-filled-undefslot three 4-filled-undefslot REST-After-This: rest#1 rest#2 END-2 END-Argument
  • 函数后面的第一个参数始终是this参数
  • 我使用Symbol.split来区分前导和尾随参数,如果直接传递包含要绑定的参数的前导和尾随数组,可能会更好。
  • undefined前导绑定参数中的值将替换为传入的参数
  • (额外的好处)绑定的函数继承了它所传递的函数的所有属性/字段。
  • 删除copyProperties & REMOVE_DESCRIPTORS以忽略行为

使用Symbol.split

const bindArguments = (()=>{
  const { defineProperty, getOwnPropertyDescriptors, defineProperties } = Object;
  const defineValue = (target, key, value) => defineProperty(target, key, { value: value });

  const $targetFn  = Symbol.for('bindArguments::[[TargetFunction]]');
  const $thisArgV  = Symbol.for('bindArguments::[[BoundThis]]');
  const $headArgs  = Symbol.for('bindArguments::[[BoundArgs]].Leading');
  const $tailArgs  = Symbol.for('bindArguments::[[BoundArgs]].Trailing');
  const EMPTY_ARRAY = Object.freeze([]);
  const REMOVE_DESCRIPTORS = [$targetFn, $thisArgV, $headArgs, $tailArgs, 'name', 'toString', 'length'];
  const copyProperties = (target, source) => {
    const descriptors = getOwnPropertyDescriptors(source);
    for(let key in descriptors) {
      if (REMOVE_DESCRIPTORS.includes(key)) { continue; }
      defineProperty(target, key, descriptors[key]);
    }
  }
  const fillUndefinedPositions = (boundArgs, args) => {
    let idxUndefined = boundArgs.indexOf(undefined);
    if (idxUndefined >= 0) {
      boundArgs = [...boundArgs];
      while (idxUndefined >= 0 && args.length > 0) {
        boundArgs[idxUndefined] = args.shift();
        idxUndefined = boundArgs.indexOf(undefined, idxUndefined+1);
      }
    }
    return boundArgs;
  };
  const boundFuncProps = {
    name:    { configurable: true, get() { return 'bound ' + this[$targetFn].name; } },
    toString:{ configurable: true, get() { return this[$targetFn].toString;  } },
    length:  { configurable: true, get() { return Math.max(0, this[$targetFn].length - this[$headArgs].length); } }
  }
  const createBoundFunction = (targetFunction, boundThis, leadingArgs, trailingArgs) => {
    defineValue(boundFunction, $targetFn, targetFunction);
    defineValue(boundFunction, $thisArgV, boundThis);
    defineValue(boundFunction, $headArgs, leadingArgs.length > 0 ? leadingArgs : EMPTY_ARRAY);
    defineValue(boundFunction, $tailArgs, trailingArgs.length > 0 ? trailingArgs : EMPTY_ARRAY);
    defineProperties(boundFunction, boundFuncProps)
    copyProperties(boundFunction, targetFunction);
    return boundFunction;

    function boundFunction(...args) {
      const targetFn = boundFunction[$targetFn];
      const thisArgV = boundFunction[$thisArgV];
      const headArgsV = boundFunction[$headArgs];
      const tailArgs = boundFunction[$tailArgs];   
      const headArgs = headArgsV.length > 0 && args.length > 0 ? fillUndefinedPositions(headArgsV, args) : headArgsV;
      const thisArg = thisArgV === undefined || this instanceof boundFunction ? this : thisArgV;
      const result = targetFn.call(thisArg, ...headArgs, ...args, ...tailArgs);
      return result;
    };
  };

  return (fn, ...bindArgs) => {
    const argsLength = bindArgs.length;
    if (argsLength === 0) { return fn; }
    Function.prototype.bind
    const idxSplit = bindArgs.indexOf(Symbol.split);
    switch(idxSplit) {
      case -1:
        return createBoundFunction(fn, bindArgs[0], bindArgs.slice(1), EMPTY_ARRAY);
      case 0:
        return createBoundFunction(fn, undefined, EMPTY_ARRAY, bindArgs.slice(1));
      case 1:
        return createBoundFunction(fn, bindArgs[0], EMPTY_ARRAY, bindArgs.slice(2));
      default:
        return createBoundFunction(fn, bindArgs[0], bindArgs.slice(1, idxSplit), bindArgs.slice(idxSplit+1));
    }
  };
})();

直接传递前导和尾随参数。

一个二个一个一个

相关问题