soft-bind 函数

什么是softBind

原生的bind函数在进行多次bind时,始终以第一次的bind为准

1
2
3
4
5
6
7
8
function foo() {
console.log(this.bar)
}

foo = foo.bind({ bar: 'first' })
foo = foo.bind({ bar: 'second' })

foo() // first

softBind就是多次bind时,后面的覆盖前面的

1
2
3
4
5
6
7
8
function foo() {
console.log(this.bar)
}

foo = foo.softBind({ bar: 'first' })
foo = foo.softBind({ bar: 'second' })

foo() // second

为什么简单使用call/apply不行

简单实现一个bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.softBind = function(obj, arg){
var that = this

return function() {
return that.call(obj, arg)
}
}
function foo() {
console.log(this.bar)
}

foo = foo.softBind({ bar: 'first' })
foo = foo.softBind({ bar: 'second' })
foo() // first

多次bind的执行就像一个栈一样,最后的bind最先执行,所以最后一次调用call绑定this的对象,永远是第一次bind的值,跟koa的「洋葱模型」也有点像。
多次bind的执行流程可以简化为下面的伪代码

1
2
3
4
5
6
foo () {
this = { bar: 'second' } // 第二次绑定
this = { bar: 'first' } // 第一次绑定

执行foo
}

softBind实现

思路如下:

  • 判断是否已经绑定,如果已经绑定,则不在绑定到新的对象

判断是否已经绑定

执行bind的时候,如果已经绑定了this,调用call/apply时直接传this,否则,传绑定的对象。

那么,如何判断是否绑定了this呢?

当未进行任何显示、隐示绑定时,this的值有3种可能:

  • undefined (严格模式下)
  • window (非严格模式下)
  • global (node)

所以,只要this不是这3者之一,那么就说明进行了绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.softBind = function (obj, ...args) {
const fn = this
const curried = args

return function (...args) {
// 如果已经绑定了this,把这个绑定向下传递
const target = ( !this ||
(typeof window !== "undefined" && this === window) ||
(typeof global !== "undefined" && this === global)
) ? obj : this

return fn.apply(
target,
[...curried, ...args]
)
}
}