【问题标题】:How does Function.bind.bind(Function.call) uncurry?Function.bind.bind(Function.call) 如何解开?
【发布时间】:2014-06-22 09:00:16
【问题描述】:

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

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

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

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

【问题讨论】:

  • 感谢偏头痛。 :P
  • 你在哪里看到的?是某个知名库的一部分,还是只是您发现的一些没有 cmets 或上下文的神奇代码行?
  • @p.s.w.g 他在 Google 找到了一份工作。
  • @djechlin 我碰巧找到了这个jsPerf。它可能有用。
  • 我认为 JavaScript 上下文中使用的术语“curry”或“uncurry”通常是不准确的;在这种情况下,我不确定它的意思。

标签: javascript functional-programming this function-binding


【解决方案1】:

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

如果你从今天早上起床后就没有不间断地喝咖啡,那么一步一步来:

  • Function.bind.bind 是对 bind 函数的引用。该引用是从bind 函数本身的属性——混淆点1——生成的。请记住,bind 函数在以某个函数作为对象调用时,用于围绕该函数创建一个包装器,其中 this 绑定到传入的第一个参数。
  • 因此,该函数调用会返回一个函数。该函数的工作方式就像您调用了Function.call.bind(something)
  • 如果您将某个随机函数作为参数传递给 that 函数,那么,您会得到一个围绕该随机函数的包装器,该包装器在被调用时的行为类似于 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() 调用。它还可以让您在高级前端开发人员职位上停留更长时间。

edit — 我将破坏上面的妙语,因为我只是想到了一个很好的理由来使用 bind+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 包装器,它可能更有意义。当然,一切都是有代价的,而且这种包装器可能会对性能产生一些影响。 (如果这样的做法很普遍,那么运行时可能会得到改进。)

【讨论】:

  • 我为你笑了我的咖啡。有什么方法可以跟随并从你那里获得更多的编程幽默?
  • @pilau ha ha 感谢您找到这个;前几天我在找它,但找不到它:)我通常没那么有趣!
  • 你有博客之类的吗?有这么多代表分数的人肯定有博客!
  • @pilau 我愿意,但我懒得写信给它。不过前几天我要写一些东西,所以也许我会得到能量。下周我要去参加一个会议,所以也许我会写一些东西:)(哦,博客是从我的个人资料链接的。)
  • 会议愉快!同时,这可能是这里的主题:stackoverflow.com/a/21792913/1049693
【解决方案2】:

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

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

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

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

现在让我们来看看你的代码:bind.call(bind, call)。在这里,您在 bind 上应用 bind,并将 call 作为 this 值:bindbind(call)。让我们将此(使用上述规则)扩展为 bindcall。如果我们现在给它提供一些参数呢?

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

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

调用 fn(context, ...)

fn上下文(…)

一步一步,我们可以做到

uncurryThis = bindbind(call)//bindcall

func = uncurryThis(method) // callmethod

result = func(context, ...) // 方法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));
    };
}

【讨论】:

    【解决方案3】:

    当我们在函数上调用 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`
    

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

    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.
    

    【讨论】:

      【解决方案4】:

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

      上下文:

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

      [‘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 返回一个函数,该函数将传递的第一个参数转换为 this 以供 String.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)
      

      仍然很混乱,但尝试写出 Classfunctioninstanceargs 是我每次应用公式 [1] 和 [2] 时的内容,这应该是有意义的.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-06
        • 1970-01-01
        • 2018-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多