前言
本来该博客应该是在js函数中单独开一个子标题来写的,不过后来发现bind函数的实现还是没有想象中的这么简单,于是就另开一篇博客进行记录。
关于bind函数
bind函数
大家都知道,在js中,有一个很是让人头疼的问题----那就是函数this
的指向问题,而js本身内置于Function
原型链中的函数call/apply/bind
就是为了方便指定this
对象而设置的,而其中bind
函数与call/apply
的不同点就是它接收一个函数,执行后它会返回一个新的函数,新的函数的this
是已经被绑定了的。-
用法确定
首先上面说了bind
函数的功能,那么现在来看看bind
函数有哪些常见的用法:-
fn.bind(context)
: 很好理解,第一个参数接收要指定的this,返回的函数的this
绑定context
。
image.png -
fn.bind(context, p1, p2)
: 除了第一个参数,后续的所有参数都是原本函数fn接收的参数
image.png -
fn.bind(context, p1, p2)(p3, p4)
: 返回出来的新函数也能接收参数,并且这些参数一样可以当做fn的参数
image.png
-
-
实现
有了上面层层递进的用法,那么现在就开始实现我们自己的bind
函数吧,我命名为bind2
。-
首先实现最简单的
fn.bind(context)
, 前面提到bind
函数会返回一个函数,这个函数的this
就是你所传入的context
,那么我们可以返回一个使用call
调用的函数,但是这里需要注意的是,因为本身bind
的调用方式是fn.bind(context)
,那么在bind
函数内部实际上存在一个this
,这个this
是fn,不要和context
给搞混了:
image.png -
实现
fn.bind(context, p1, p2)
: 这个需求很简单,我们只需要将bind
函数接收的参数中的除第一位的参数全部传入到内部返回的函数即可,如下:
image.png -
实现
fn.bind(context, p1, p2)(p3, p4)
,这个需求和第二个需求类似,只是接收参数的位置变成了bind
返回出来的函数,那么只需要将返回出来的函数的参数也传递进去this.call
里面就好了:
image.png
-
这样我们就实现了一个简单的bind
函数,这个函数和js函数中实现的bind是一模一样的,但现在bind2
依旧存在一个问题,无法使用new
对其返回的函数进行操作。
兼容new操作符
要解决上节提到的不支持new
操作符的问题,我们需要先大概了解一下,new
操作符就是做了什么。
- new操作符究竟做了什么?
大家都知道,new
操作符用于针对一个构造函数生成对应的新的实例,例如下面这个:
image.png
但是实际上它执行了4步操作(以上诉例子为例):
- 声明一个空的对象
{}
- 将构造函数A的
prototype
赋值给空对象的__proto__
- 执行构造函数A,并绑定this为空对象
- 构造函数A中返回
this
,注意这时候函数A中的this
已经被绑定为空对象
所以也就等价于下面代码:
image.png
在原生的bind
函数中,对返回出来的函数执行new
,结果是符合预期的:
但是我们自己实现的
bind2
却不符合这个预期,并且还产生了报错:- 实现兼容new操作符的bind函数
由上述可知,bind2
函数无法兼容new
,首先需要解决的就是上面的报错,报错原因是箭头函数内部不存在this
,所以不支持使用new
操作符,那么我们可以改成如下:
image.png
但是这时候又产生了一个新的问题,new
出来的实例是一个空的对象,而p1
则被赋值到对象n上去了:
image.png
还记得上面说的new
操作符所做的四个步骤吗?在第四步的时候需要返回那个创建出来的空对象,但是在上面例子中我们new
的实际上bind2
返回的那个函数,而该函数则返回了fn.call(context, ...args, ...rest)
,结果就导致new
创建出来的空对象没有被返回,又因为fn.call(context)
绑定了this
为n,所以this.p1 = p1
被作用到对象n中去了。
那么要如何解决这个问题呢? 只要把fn.call(context)
中的context
设定回空对象就好了,但是这个空对象在哪里呢?答案是这个空对象就是函数result
中的this
:
所以可以改成如下:
但这时候又有新的问题产生,fn.call·的第一个参数什么时候用
result内部的
this,什么时候用
context? 我们可以通过判断返回出来的函数是不是调用了
new操作符来解决这个问题,用了
new就传入
this,没有用则传入
context,那如何判断一个函数是不是被
new调用的呢? 前面关于
new`操作符做的事也说了:
将构造函数A的
prototype
赋值给空对象的__proto__
那么在这里就可以通过判断函数result
中的this
的__proto__
是不是与result
本身的prototype
相等,来确定是不是使用了new
用以解决上面的问题,所以代码如下:
这样上述的所有需求就都可以达成了。
- 新的问题
按照上面的写法,实际上已经满足了bind
函数的大部分功能,但是依旧存在一个问题,比如我在函数test
的prototype
上加上一个方法,使用原生bind
函数的时候,new
出来的实例是拥有这个方法的:
image.png
但是使用我们自己实现的bind2
函数时则会打印出undefined
:
image.png
原因何在?
因为还是出在这一句:
将构造函数A的
prototype
赋值给空对象的__proto__
对应到我们的例子中就是,空对象的__proto__
被指向函数result
的prototype
上了,而不是函数test
:
知道原因后解决问题也很简单,只要将函数
result
的prototype
指向test
的prototype
即可,而bind2
函数中就是fn
,所以代码如下:到此,我们的bind
函数就实现了,如果还发现有什么问题,也欢迎大家在留言榜中提出。