我们的代码库中有这样一行代码:
var uncurryThis = Function.bind.bind(Function.call);
我正在努力解决的问题。大概是将其拆分开来。我该怎么做才能解决这个问题?
我猜这是Function.bind
的一个版本,它自己的this
被绑定到了Function.call
。但这并没有帮助我太多。而且我也没找到任何用途,所以我甚至不确定你是要单独调用它还是需要首先“将其绑定为方法”,你懂的,先绑定再调用。
我们的代码库中有这样一行代码:
var uncurryThis = Function.bind.bind(Function.call);
我正在努力解决的问题。大概是将其拆分开来。我该怎么做才能解决这个问题?
我猜这是Function.bind
的一个版本,它自己的this
被绑定到了Function.call
。但这并没有帮助我太多。而且我也没找到任何用途,所以我甚至不确定你是要单独调用它还是需要首先“将其绑定为方法”,你懂的,先绑定再调用。
它将call
函数传递给bind
函数,其中bind
函数本身是this
的值。因此,当你调用它时,你会得到一个包装bind
函数的对象,它会安排this
为call
函数。那个又是一个函数,让你可以创建一个围绕你传递给它的参数绑定的call
函数的包装器。
假设你从今天早上起就没有持续饮用咖啡,那么我们来一步一步地看:
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);
不过这个神奇的"绑定-绑定-调用"技巧为你提供了一种廉价的方法来创建一个变体函数,让你避免显式地编写.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()
封装,可能会更有意义。当然,万物都有代价,这些包装器很可能会影响性能。(如果这样的做法很常见,那么运行时可能会得到改善。)
好的。你知道 bind
是什么吗?它是函数的一个方法,用于绑定它们的this
参数,并返回一个新的函数。它可以简化为:
function bind(context) {
var fn = this;
return function() {
return fn.apply(context, arguments);
};
}
call
接受一个 this
值,但它不像返回一个函数,而是立即应用它:callfn(context, …) -> fncontext(…)。bind.call(bind, call)
。在这里,你正在将 bind
应用于 bind
,并将 call
作为 this 值:bindbind(call)。让我们扩展此内容(按照上述规则),以得到 bindcall。如果现在我们向其提供了一些参数呢?
逐步进行,我们可以这样做:bindbind(call) (fn)(context, …)
bindcall (fn)(context, …)
call fn(context, …)
fncontext(…)
这个方法的一个实际用例是任何应该被转换为静态函数(以对象作为第一个参数调用方法)的 "类" 方法。uncurryThis = bindbind(call)
//
bindcallfunc = uncurryThis(method)
//
callmethodresult = func(context, …)
//
methodcontext(…)
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));
};
}
如果你倒着思考,我认为这个问题可以更清晰地解释。
背景:
假设我们想要将一个字符串数组转换为小写。可以像下面这样实现:
[‘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)
虽然仍然很难理解,但每次应用公式[1]和[2]时,尝试写出类
、函数
、实例
和参数
,这样就会有意义了。
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
函数的简化版本):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.
:P
- Matt BallFunction.call === f.call === Function.prototype.call
。 - Nathan Chappell