uncurryThis
故事背景是这样的。
难得在项目搞完送去打包之后,得到了一段空闲时间,于是想看一看 babel 是怎么写 Set 的 polyfill。最后并没有看完,因为很快新的需求又来了。
但是在这个过程中还是看到了一段很有趣的代码。就是 uncurryThis。记不住所以省略了 !bind 的情况。babel 返回的是三目运算符的结果。
const FunctionProto = Function.prototype
const call = FunctionProto.call
const bind = FUnctionProto.bind
export const uncurryThis = bind.bind(call)
然后就可以这样使用。
const toUpperCase = uncurryThis(String.prototype.toUpperCase)
toUpperCase('abc') // --> 'ABC'
// or
function bar() {
console.log(this.foo)
}
uncurryThis(bar)({ foo: 'hello' }) // 打印 'hello'
相信不少人在一开始 bind.bind(call) 的时候已经晕了,但是先不说,这样一通操作之后,用起来就很奇妙。magically use --/
接下来一步步解剖 bind.bind(call) 里面究竟发生了什么。
首先我们看一下普通的 bind 是怎么使用的。
const fn = Object.prototype.toString.bind([]) // 将对象原型的 toString 方法给 [] 数组使用
fn() 的时候得到 '[object Array]'
bind 是生成一个函数,这个函数的 this 是 bind 时的第一个参数。内部实现等价于
function bind(context) {
// 调用 bind 的函数 就是 xxx.bind
const fn = this
return function () {
// 绑定this,并且将新函数的参数作为参数。
// 调用bind封装的函数依然有原函数的返回操作,跟随原函数的返回结果
return fn.apply(context, arguments)
}
}
所以当我们 bind.bind(call) 的时候,就是返回了一个将 call 作为 bind 的 this 一个新的函数。当我们调用这个新的函数时返回的是 bind 生成的一个新函数,绑定了 arguments。
bind.bind(call) 得到:
function () {
return bind.apply(call, arguments)
}
然后调用这个函数 得到:arguments[0] 是传入要封装的函数
bind.apply(call, arguments) 的执行结果 // 已经把参数绑上了,这也是一个bind
即 call.bind(...arguments) 再包装了一层bind
使用的时候其实就等价于 上面的arguments[0].call(这次调用时的传参)
用上面的例子就是等价于:
String.prototype.toUpperCase.call('abc')
bar.call({ foo: 'hello' })
其实就是用call显式的进行改绑和隐式的进行改绑。但是 bind.bind(call) 毕竟还是影响性能的。