前言
听说bind有点特别啊,一开始初学不知道,以为那么简单的东西就绑成函数等待执行呗,后来发现这玩意实际还是比较复杂的。
mdn上Polyfill
if ( ! Function. prototype. bind) ( function ( ) {
var slice = Array. prototype. slice;
Function. prototype. bind = function ( ) {
var thatFunc = this , thatArg = arguments[ 0 ] ;
var args = slice. call ( arguments, 1 ) ;
if ( typeof thatFunc !== 'function' ) {
throw new TypeError ( 'Function.prototype.bind - ' +
'what is trying to be bound is not callable' ) ;
}
return function ( ) {
var funcArgs = args. concat ( slice. call ( arguments) )
return thatFunc. apply ( thatArg, funcArgs) ;
} ;
} ;
} ) ( ) ;
前面有个注释,这个不支持在用new来生成绑定函数。也就是说,原版的bind可以new的。 当然原版还有很多特别的地方。写几个小例子测试下原版。
原版测试
function aaa ( ) { }
ccc = aaa. bind ( 'ddd' )
console. log ( ccc. __proto__)
console. log ( ccc. constructor)
console. log ( ccc. prototype)
这个就是只有一个函数然后直接bind情况。这3个打印分别是Function.prototype,Function,undefined。 就是这个undefined表示了函数生成的bind函数,无prototype。 这好像暗示我们bind内部用箭头函数实现,但是,箭头函数不能new啊。 使用传参形式结合new看看效果:
function aaa ( x, y) {
this . x= x
this . y= y
}
aaa. prototype. getXY = function ( ) {
return this . x+ this . y
}
let bindfunc = aaa. bind ( null , 11 )
let ins = new bindfunc ( 33 )
console. log ( ins. getXY ( ) )
console. log ( ins instanceof bindfunc )
console. log ( ins instanceof aaa )
console. log ( ins. constructor)
console. log ( bindfunc. constructor)
console. log ( bindfunc. __proto__)
console. log ( bindfunc. prototype)
通过console其实可以推断,bind所返回的函数的prototype,应该是能通过原型链找到原函数的prototype的。而且如果new的话,this不是指向绑定this,而是指向实例本身(这个就有点跟bind设计的意思冲突,用bind想改变this,结果new的时候this又不改变)。 正常的函数,new出来实例的constructor会指向这个函数。所以正常来说,这个Ins的constructor应该指向bindfunc才对。所以bindfunc的prototype被调换过了。 看一下mdn推荐的大神写的bind打印结果也和真正的bind不一样。大神写的bind地址 用上面那个例子测试大神写的这个bind,会发现打印结果:
44
true
true
[Function: aaa]
[Function: Function]
[Function] { mybind: [Function: mybind] }
aaa {}
大神除了undefined问题,其他基本上都跟原版打印的一样。
实现bind
对于这种问题,最好就是画个图,然后照着图连就行了。这里我画了个图: 可以发现,如果用Js实现bind,undefined问题是无法避免的,因为你能new,而且必须让实例找到原函数上,就得设置bindfunc的prototype。不然你new出来的实例找不到路。虽然js可以通过给bindfunc设置代理,让其prototype的打印值为undefined,但是这样你返回的函数就是代理对象,而不是原来那个绑定函数。 我们可以通过instanceof 判断是不是被new调用,如果是new 调用,那么外面那个this是原函数,里面那个this是new出来的对象。 最后可以把前面大神写的简略点,改成这样:
Function. prototype. mybind = function ( context, ... args) {
let fn = this
if ( typeof fn !== 'function' ) {
throw 'error'
}
let fbound = function ( ... innerArgs) {
return fn. apply (
this instanceof fn ? this : context, [ ... args, ... innerArgs]
)
}
fbound. prototype = Object. create ( fn. prototype)
return fbound
}
function aaa ( x, y) {
this . x = x
this . y = y
}
aaa. prototype. getXY = function ( ) {
return this . x + this . y
}
let bindfunc = aaa. mybind ( null , 11 )
let ins = new bindfunc ( 33 )
console. log ( ins. getXY ( ) )
console. log ( ins instanceof bindfunc )
console. log ( ins instanceof aaa )
console. log ( ins. constructor)
console. log ( bindfunc. constructor)
console. log ( bindfunc. __proto__)
console. log ( bindfunc. prototype)