bind.bind“是什么意思?使用JavaScript bind的一种奇怪方式

llew8vvj  于 2023-03-16  发布在  Java
关注(0)|答案(5)|浏览(120)

我正在阅读一本关于编写JavaScript框架的书,发现了这个代码片段。但是我不明白它是如何工作的,尤其是bind.bind的用法?有人知道吗?

var bind = Function.prototype.bind;
var apply = bind.bind(bind.apply);
var fn = apply([].concat);
var a = [1, 2, 3], b = [4, [5, 6], 7];
fn(a, b);
//output [1, 2, 3, 4, 5, 6, 7]
gpfsuwkq

gpfsuwkq1#

这让我想起了解方程和展开方程的日子。

1 .首先,让我们展开第一个apply函数:

var bind = Function.prototype.bind;
var apply = bind.bind(bind.apply);
var fn = apply([].concat);

转变为:

var apply = Function.prototype.bind.bind(Function.prototype.bind.apply);
var fn = apply([].concat)

2 .其次,我们扩展了fn函数:

var fn = Function.prototype.bind.bind(Function.prototype.bind.apply)([].concat);

3 .我们现在发明了一个js代数规则,并用call调用替换bind.bind...()调用。

实际上,我们基于以下概念实现替换:

someFunction.bind(arg1)(arg2) <==> someFunction.call(arg1, arg2)

因此,我们可以将其替换为:

var fn = Function.prototype.bind.call(Function.prototype.bind.apply, [].concat);

4 .对于我们的第二个js代数规则,我们设计如下:

someFn.bind.call(target, ...) <==> target.bind(...) .
someFn在这里并不重要,因为我们没有在它上面调用bind(),我们在bind上调用call-替换原来的someFn,因此它被替换为target
因此,我们将bind.call(target)替换为target.bind替代项

var fn = Function.prototype.bind.apply.bind([].concat)

5 .如果最后一个置换也执行invocation(),我们可以执行如下替换:

fn([1, 2], [3, 4]) <==> [].concat.apply([1, 2], [3, 4])

但是我们只有绑定,没有可以替换的调用,等价于:

var fn = function (arg1, arg2) { 
    return [].concat.apply(arg1, arg2); 
}
// instead arg1 and arg2 we could use more accurate arguments logic also.

最终结果

var fn = Function.prototype.bind.apply.bind([].concat) 

// or

var fn = function (arg1, arg2) { 
    return [].concat.apply(arg1, arg2); 
}

fn函数继承了concat函数,让我们以函数的方式使用它,而不是从对象调用它。fn不是将concat绑定到调用者的this,而是将它作为thisarg2应用到arg1上,作为连接到arg1的其他参数。

fn([1, 2], [3, [5, 6], 4])
// [1, 2, 3, 5, 6, 4]
ffscu2ro

ffscu2ro2#

因为Function.prototype.bind本身是一个函数,所以它将自身继承为一个方法。
通常,bind是作为一个特定函数的示例方法调用的,但是我们可以将它重新绑定到Function.prototype.apply并返回一个更高阶的函数。
一种不那么简洁的写法是:

function apply (fn) {
  return function (a, b) {
    return fn.apply(a, b)
  }
}
cgyqldqp

cgyqldqp3#

我们必须稍微考虑一下bind在幕后的实际作用。粗略地说,它的工作原理是这样的:

function bind(fn, ...bound) {
    return (...other) => this.call(fn, ...bound, ...other);
}

(请记住,词法this指的是调用bind的函数。)

apply = bind.bind(bind.apply);

手动展开bind,我们得到:

apply = (...other) => bind.call(bind.apply, ...other);

我们只对传递一个参数感兴趣,这个参数是一个函数,希望这也意味着它的apply属性和bind相同:

apply = (fn) => bind.call(fn.apply, fn);

但是bind(希望)本身也在fn.apply的原型上,所以我们可以进一步简化为:

apply = (fn) => fn.apply.bind(fn);

现在我们可以再次扩展bind了:

apply = (fn) => (...other) => fn.apply.call(fn, ...other);

这一次,我们需要两个参数,对call的调用也可以简化:

apply = (fn) => (obj, args) => fn.apply(obj, args);

现在我们可以在[].concat上调用apply了:

fn = (obj, args) => [].concat.apply(obj, args);

obj需要是一个数组才能正常工作,因此简化为:

fn = (obj, args) => obj.concat(...args);

在所提供的示例中,我们得到了

[1, 2, 3].concat(4, [5, 6], 7)

其返回期望的结果。

ia2d9nvy

ia2d9nvy4#

bind()方法为传递到bind()的值创建函数的新示例。
在这里,我们通过分解成单独的可执行代码单元来调用Function.prototype.bind.apply(Function.prototype, [null].concat(arguments));
目的是调用apply;将原始函数作为第一个参数传递,然后作为参数数组传递。
这些代码对于每个涉及的进程都有不同的变量,以实现与动态参数的绑定。

var bind=Function.prototype.bind; 
  // the bind function is assigned to a var

var apply=bind.bind(bind.apply); 
  // a new function apply is created which is nothing but bind.apply

var fn=apply([].concat); 
  // the apply function is defined and now we have final function 
   that will concat array.

最后两行用于通过传递两个参数来执行fn函数
var a=[1,2,3],b=[4,[5,6],7]; fn(a,b);

ryoqjall

ryoqjall5#

示例说明

下面的技术可以用来“uncurrythis",意思是将一个方法转换成一个函数,其中this作为第一个参数提供,而不是基于方法调用的对象隐式地提供this

const map = new Map()
const set = Map.prototype.set
// ------------------------------
{ // STEP 0
  map.set(2, 'b') // normal method call
  // => depends on the `Map.prototype.set` not being corrupted to work
  // e.g. Map.prototype.set = undefined // breaks this line of code
}
// ------------------------------
{ // STEP 1
  // just as `map` in e.g. `map.set(...)`
  set.call(map, 2, 'b') // can pass `this` as param now (="uncurry this")
  // the first arg `map` becomes `this` of `set(...)`
  // => but depends on `Function.prototype.call` not being corrupted to work
  // e.g. Function.prototype.call = undefined // breaks this line of code
}
// ------------------------------
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
const call = Function.prototype.call
{ // STEP 2
  // just as `map` in e.g. `set.call(map, ...)`
  const set_call = call.bind(set) // think: "set.call(...)" in one function
  // the first arg `set` later becomes `this` of `set_call(...)`
  // to make `set.call` a **"single set_call function"**
  // => is the same as step 1, but we just use `bind`
  set_call(map, 2, 'b') // can pass `this` as a param now
  // => almost perfect, but every time we want to make another
  //    uncurried function e.g. `xyz_call(...)`
  //    instead of `xyz.call(...)
  //    we need to call `call.bind` again, which depends on prototypes
  // e.g. Function.prototype.bind = undefined
  // => breaks our "uncurried function making technique"
}
// ------------------------------
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
const bind = Function.prototype.bind
{ // STEP 3
  // just as `set` in e.g. `call.bind(set)` to get `set_call`
  const call_bind = bind.bind(call) // corrupt prototype has no effect later
  // the first arg `call` later becomes `this` of `call_bind(...)`
  // to make `call.bind` a **"single call_bind function"**
  // =>
  // we make a "custom bound `bind`" with the first argument of `bind`
  // already set to `call`, hence `bind.bind(call)` => `call_bind`
  // => a `bind` where the "bind's `this`" is `call`
  // => as in `call.bind(...)` ... :-) yay!
  // ----------------------------------------
  // One more time (with different words):
  // This is basically the same as in step 2:
  // But instead of calling `call.bind(...) immediately on something
  // we bind `call` as the `this` for a function arg we provide later
  // so instead of `call.bind(set)` to make `set_call`,
  // we now can use our single helper function `call_bind(...)`
  const set_call = call_bind(set)
  set_call(map, 2, 'b')
}

// It might read funky :-), but it works in the exact same way:
// 1. While the former turned `set.call(...)` into a single function
// 2. The latter turns `call.bind(...)` into a single function
// =>
// `set_call` allows to "uncurry" `map.set`
// and we can now do `set_call(map,...)`
//
// `call_bind` allows to "uncurry" `call.bind`
// and we can now do `set_call = call_bind(set)`

// ------------------------------
// SUMMARY:

// So we went from
/*0.*/map.set(2, 'b')
// to 
/*2.*/set.call(map, 2, 'b')
// to
/*3.*/call.bind(set)(map, 2, 'b')
// to
/*4.*/bind.bind(call)(set)(map, 2, 'b')

// No prototype corruption will break anything now,
// because we have direct references to all functions
// and never have to call methods again
// which depend on prototypes not being broken.

// All we need to do once initially is:
{
  const { set } = Map.prototype.set
  const { bind, call } = Object.prototype
  const call_bind = bind.bind(call)
  // basically `call_bind` (a.k.a `uncurryThis`) is a single
  // uncorruptable function to create
  // as many uncurried functions later as we like :-)
  const map = new Map()
  // and now instead of
  map.set(2, 'b')
  // we can do without prototype dependent method calls
  const set_call = bind_call(set)
  set_call(map, 2, 'b')
}
// `set` and `map` and `set_call` and `bind_call` will always work
// no matter if prototypes change later or not.
// they do not depend on the prototypes anymore

如果想更深入地了解,我可以推荐:http://web.archive.org/web/20160805225710/http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming

相关问题