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,这显然不对
用一个空对象作为中介即可解决