手写 bind

371 阅读2分钟

bind

我们知道,bind 也可以绑定 this,并且不是立即执行,而是返回一个函数,这其中提到了柯里化的概念

function kl(x){
    return function(y){
        return x + y
    }
}

let test=kl(1)
test(2)

// 3

这也是闭包的一种体现,由于先前的赋值,导致 test 变量保持对返回的函数的引用,而此函数的返回值需要用到 x,这样 x 就不会被回收

这好像很像 bind 呀,让我们先改装一下,让它支持绑定 this

Function.prototype.bind2=function(context){
    let self=this
    let arg1=[...arguments].slice(1)
    return function(){
        let arg2=[...arguments]
        return self.apply(context,arg1.concat(arg2))
    }
}

let value=5
let foo={
    value:10
}
function print(){
    console.log(this.value)
}
print.bind(foo)()    // 10

用 self 来保存 this 是因为要指向调用 bind2 的函数,便于最后调用,参数也不能忘记加上,apply 是不是很好用?

看起来已经完成了,但是函数也可以用来构造对象,我们来测试一下我们的 bind

let foo={
    value:10
}

function Foo(name,age){
    this.name=name
    this.age=age
}
Foo.prototype.color='black'

let bindFoo=Foo.bind2(foo,'小红')
let foo=new bindFoo('18')
foo.color

// undefined

诶,实例为什么访问不到原型上的属性呢,按理说 foo 的原型和 bindFoo 的 prototype 指向同一个对象

我们都知道 new 操作符的过程,第一步就是创建一个新对象,让其原型指向构造函数的 prototype,所以在本例中 foo 的原型指向 bindFoo 的 prototype,这和 Foo 的 prototype 根本不沾边呀,所以我们需要解决一下原型链的指向问题

Function.prototype.bind2=function(context){
    let self=this
    let arg1=[...arguments].slice(1)
    let fBound=function(){
        let arg2=[...arguments]
        return self.apply(this instanceof fBound ? this : context
                 ,arg1.concat(arg2))
    }
    function Tmp(){}
    Tmp.prototype=this.prototype
    fBound.prototype=new Tmp()
    return fBound
}

我们在执行函数时需要判断调用者的意图,如果是作为构造函数来生成对象,那么此处 this 指向新的实例,若只是普通的函数执行,那么 this 即为传入的对象,否则忽略,这也是原生 bind 的做法

我们最好不要直接将构造函数的 prototype 指向 this.prototype,虽然解决了上一个问题

但是第一,我们以后对于构造函数 prototype 的更改会触发 this.prototype 的变动

第二,构造函数的 prototype 的 constructor 的指向是 Foo,而不是 bindFoo,这显然不对

用一个空对象作为中介即可解决