14

我们的代码库中有这一行:

var uncurryThis = Function.bind.bind(Function.call);

我正在努力解决。大概,它不咖喱。我该如何解决?

我想这是一个Function.bind自己的版本this绑定到Function.call. 对我帮助不够。而且我还没有找到任何用途,所以我什至不确定你是否将它称为独立的或需要将其称为“作为方法”,只是,你知道,先绑定它。

4

4 回答 4

15

它将函数传递callbind函数,bind函数本身就是 的值this。因此,您会得到一个围绕该函数的包装器,该包装器在您调用它时bind安排为该this函数。反过来,that 是一个函数,它允许您围绕绑定到您传递的某个参数的函数创建一个包装callcall

如果你今天早上起床后没有不停地喝咖啡,一步一步:

  • Function.bind.bind是对bind函数的引用。bind引用是从函数本身的属性——混淆点 1——生成的。请记住,bind当使用某个函数作为对象调用该函数时,该函数用于围绕该函数创建一个包装器,并this绑定到传入的第一个参数。
  • 因此,该函数调用为您提供了一个函数。该函数的工作方式就像您调用了Function.call.bind(something).
  • 如果您将某个随机函数作为参数传递给函数,那么您将返回一个围绕该随机函数的包装器,该包装器在调用时的行为类似于randomFunction.call(whatever).

所以:

function random() {
  alert(this.foo);
}

var bb = Function.bind.bind(Function.call);

var randomcall = bb(random);

randomcall({ foo: "hello world" }); // alerts "hello world"

最终的一点是:你有一个函数,在函数内部有一些期望this有一些属性的代码,它this以一种或另一种方式使用。您真的希望能够将这个函数与这里的某个对象、那里的某个对象一起使用。你显然可以做到这一点

random.call(someObject);

但是这个神奇的“bind-bind-call”技巧为你提供了一种廉价的方法来创建你的函数的变体,让你避免显式编码调用.call(). 它还允许您在高级前端开发人员职位上停留更长时间。

编辑——我将破坏上面的妙语,因为我只是想到了一个很好的理由来使用绑定+调用技巧来获得一个函数,该函数安排调用一些期望通过this某个“所有者”操作的期望函数“ 目的。假设您有一个字符串数组,并且您希望获得这些字符串的小写版本。你可以这样写:

var uc = ["Hello", "World"];
var lc = uc.map(function(s) { return s.toLowerCase(); });

但是使用神奇的“bb”函数,我们也可以这样写:

var uc = ["Hello", "World"];    
var tlc = bb(String.prototype.toLowerCase);
var lc = uc.map(tlc);

以这种方式编写并没有太大的改进,但如果要为所有方便的 String 原型方法制作一组bb()-ified 包装器,它可能更有意义。当然,一切都是有代价的,而且这种包装器可能会对性能产生一些影响。(如果这样的做法很普遍,那么运行时可能会得到改善。)

于 2014-05-05T22:36:48.520 回答
6

好的。你知道做什么bind吗?这是函数的一种方法来修复它们的this参数,并返回一个新函数。可以简化为:

function bind(context) {
    var fn = this;
    return function() {
        return fn.apply(context, arguments);
    };
}

我将以更实用的风格和大量的部分应用程序来缩写带有上下文的函数调用: bind fn (context) -> fn context。带参数:(bind fn (context))(...) 等于 fn context (...)。

类似地,call确实接受一个this值,但不是返回一个函数,而是立即应用它:调用fn (context, ...) -> fn context (...)。

所以现在让我们看看你的代码:bind.call(bind, call). 在这里,您将使用bindwithbind作为callthis 值:绑定绑定(调用)。让我们将此(使用上述规则)扩展为绑定call。如果我们现在给它提供一些论据呢?

绑定绑定(调用)(fn)(上下文,...)

绑定调用 (fn)(context, …)

调用fn(上下文,...)

fn上下文(…)

一步一步,我们可以做到

uncurryThis = bind绑定(调用)//绑定调用

func = uncurryThis(method)//调用方法

结果 = func(context, ...)//方法上下文(...)

一个实际用例是任何应该转换为静态函数的“类”方法,将对象(将调用该方法)作为第一个参数:

var uncurryThis = Function.bind.bind(Function.call);
var uc = uncurryThis(String.prototype.toUpperCase);
uc("hello") // in contrast to "hello".toUpperCase()

如果您无法进行方法调用但需要静态函数,这会很有帮助;例如在

["hello", "world"].map(uc) // imagine the necessary function expression

此外,您要调用的方法可能不是对象本身的方法,如

var slice = uncurryThis(Array.prototype.slice);
slice(arguments) // instead of `Array.prototype.slice.call(arguments)` everywhere

如果有帮助,这里也是一个显式实现,没有任何绑定:

function uncurryThis(method) {
    return function(context/*, ...*/)
        return method.apply(context, Array.prototype.slice.call(arguments, 1));
    };
}
于 2014-05-06T00:33:30.940 回答
0

当我们在函数上调用 bind 时,它会返回一个新函数,并将 this 替换为上下文:

function random() {
  alert(this.foo);
}
var newRandom = random.bind({foo:"hello world"}) //return new function same as //`random` with `this` is replaced by object {foo:"hello world"}

我们也有:

Function.bind.bind(Function.call)
// return new Function.bind with its `this` is replaced by `Function.call`

它有以下来源(使用bind@Bergi给出的函数的简化版本):

var bb = function bind(context){
  var fn = Function.call;
  return function() {
        return Function.call.apply(context, arguments); //also replace fn here for easier reading
    };
}

请注意,这里的上下文将是函数,例如random,所以我们调用 bb(random) 我们的newRandom函数为:

newRandom = function(){
   return Function.call.apply(random, arguments); //also replace 
}
//`apply` function replace `this` of Function.call to `random`, and apply Function(now become `random`) with arguments in `arguments` array.
于 2015-06-01T02:01:00.567 回答
0

我认为如果您向后工作,可以更清楚地解释这一点。

语境:

假设我们想要小写一个字符串数组。这可以这样做:

[‘A’, ‘B’].map(s => s.toLowerCase())

比方说,无论出于何种原因,我都想让这个调用更通用。我不喜欢如何s绑定到this和胖箭头绑定到toLowerCase()

这个怎么样?

[‘A’, ‘B’].map(String.prototype.toLowerCase)

好吧,这不起作用,因为map将元素作为第一个参数传递,但String.prototype.toLowerCase不接受任何参数。它期望输入字符串作为this.

所以一个问题是我们可以创建一个wrapper函数来实现这个功能吗?

[‘A’, ‘B’].map(wrapper(String.prototype.toLowerCase))

wrapper返回一个函数,该函数将传递的第一个参数转换thisString.prototype.toLowerCase供使用。

我声称你的uncurryThis === wrapper.


证明:

因此,我们不要试图unCurryThis一下子全部理解。相反,让我们使用一些公式来转换unCurryThis成更容易理解的东西。

先说几个公式:

instance.function(...args)
=== (instance.constructor.prototype).function.call(instance, ...args)
=== (Class.prototype).function.call(instance, ...args) [1]
=== (Class.prototype).function.bind(instance)(...args) [2]

例如,

Class === String
instance === 'STRING'
function === toLowerCase
args === []
---
'string'.toLowerCase()
=== ('STRING'.constructor.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.bind('STRING')()

因此,让我们盲目地应用这些公式,而不必担心令人困惑的uncurryThis样子:

'string'
=== (wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING')

// Function.bind is not really the generic form because it's not using the prototype
// Here Function is an instance of a Function and not the constructor.prototype
// It is similar to calling Array.bind or someFunction.bind
// a more correct version would be
// someFunction.constructor.prototype.bind === Function.prototype.bind, so
=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING')

// Apply formula 2
// instance.function(...args) === (Class.prototype).function.bind(instance)(...args) [2]
// Class === Function
// function === bind
// instance === Function.prototype.call
// ...args === String.prototype.toLowerCase
=== instance.function(...args)('STRING')
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING')

// Apply formula 2 again
// Class == Function
// function == call
// instance === String.prototype.toLowerCase
// ...args === 'STRING'
=== instance.function(...args)
=== (String.prototype.toLowerCase).call('STRING')

// Apply formula 1
instance.function(...args) === (Class.prototype).function.call(instance, ...args) [1]
// Class === String
// function === toLowerCase
// instance === 'STRING'
// args === []
=== instance.function(...args)
=== 'STRING'.toLowerCase(...[])
=== 'STRING'.toLowerCase()

// So we have
(wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== 'STRING'.toLowerCase()
=== 'string'

反向证明:

所以你可能想知道“这家伙是怎么推导出这个uncurryThis函数的”?

你可以反转证明来推导它。我只是从上面复制方程式,但反过来:

'STRING'.toLowerCase()
=== (String.prototype.toLowerCase).call('STRING') // apply formula [1]
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING') // apply formula [2]

// At this point, you might wonder why `uncurryThis !== (Function.prototype.call).bind)
// since it also takes (String.prototype.toLowerCase)('STRING')
// This is because passing in (Function.prototype.call).bind) as an argument
// is the same as passing in Function.prototype.bind
// `this` binding isn't done unless you call
// (Function.prototype.call).bind)(String.prototype.toLowerCase)
// at that exact moment.
// If you want to be able to pass unCurryThis as a function, you need to bind the
// Function.prototype.call to the Function.prototype.bind.

=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING') // apply formula 2
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING') // un-generic-ize
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (wrapper)(String.prototype.toLowerCase)('STRING')

=>

unCurryThis === wrapper === Function.bind.bind(Function.call)

仍然很混乱,但每次我应用公式 [1] 和 [2] 时,试着写出什么Class, function,instance和are ,这应该是有意义的。args

于 2019-01-26T05:21:35.167 回答