bind函数的实现

前言

本来该博客应该是在js函数中单独开一个子标题来写的,不过后来发现bind函数的实现还是没有想象中的这么简单,于是就另开一篇博客进行记录。

关于bind函数

  • bind函数
    大家都知道,在js中,有一个很是让人头疼的问题----那就是函数this的指向问题,而js本身内置于Function原型链中的函数call/apply/bind就是为了方便指定this对象而设置的,而其中bind函数与call/apply的不同点就是它接收一个函数,执行后它会返回一个新的函数,新的函数的this是已经被绑定了的。

  • 用法确定
    首先上面说了bind函数的功能,那么现在来看看bind函数有哪些常见的用法:

    1. fn.bind(context): 很好理解,第一个参数接收要指定的this,返回的函数的this绑定context

      image.png

    2. fn.bind(context, p1, p2): 除了第一个参数,后续的所有参数都是原本函数fn接收的参数

      image.png

    3. fn.bind(context, p1, p2)(p3, p4): 返回出来的新函数也能接收参数,并且这些参数一样可以当做fn的参数

      image.png

  • 实现
    有了上面层层递进的用法,那么现在就开始实现我们自己的bind函数吧,我命名为bind2

    1. 首先实现最简单的fn.bind(context), 前面提到bind函数会返回一个函数,这个函数的this就是你所传入的context,那么我们可以返回一个使用call调用的函数,但是这里需要注意的是,因为本身bind的调用方式是fn.bind(context),那么在bind函数内部实际上存在一个this,这个this是fn,不要和context给搞混了:

      image.png

    2. 实现fn.bind(context, p1, p2): 这个需求很简单,我们只需要将bind函数接收的参数中的除第一位的参数全部传入到内部返回的函数即可,如下:

      image.png

    3. 实现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步操作(以上诉例子为例):
  1. 声明一个空的对象{}
  2. 将构造函数A的prototype赋值给空对象的__proto__
  3. 执行构造函数A,并绑定this为空对象
  4. 构造函数A中返回this,注意这时候函数A中的this已经被绑定为空对象
    所以也就等价于下面代码:
    image.png

在原生的bind函数中,对返回出来的函数执行new,结果是符合预期的:

image.png

但是我们自己实现的bind2却不符合这个预期,并且还产生了报错:
image.png

  • 实现兼容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:

image.png

所以可以改成如下:


image.png

但这时候又有新的问题产生,fn.call·的第一个参数什么时候用result内部的this,什么时候用context? 我们可以通过判断返回出来的函数是不是调用了new操作符来解决这个问题,用了new就传入this,没有用则传入context,那如何判断一个函数是不是被new调用的呢? 前面关于new`操作符做的事也说了:

将构造函数A的prototype赋值给空对象的__proto__

那么在这里就可以通过判断函数result中的this__proto__是不是与result本身的prototype相等,来确定是不是使用了new用以解决上面的问题,所以代码如下:

image.png

这样上述的所有需求就都可以达成了。

  • 新的问题
    按照上面的写法,实际上已经满足了bind函数的大部分功能,但是依旧存在一个问题,比如我在函数testprototype上加上一个方法,使用原生bind函数的时候,new出来的实例是拥有这个方法的:
    image.png

    但是使用我们自己实现的bind2函数时则会打印出undefined:
    image.png

原因何在?

因为还是出在这一句:

将构造函数A的prototype赋值给空对象的__proto__

对应到我们的例子中就是,空对象的__proto__被指向函数resultprototype上了,而不是函数test:

image.png

知道原因后解决问题也很简单,只要将函数resultprototype指向testprototype即可,而bind2函数中就是fn,所以代码如下:
image.png

到此,我们的bind函数就实现了,如果还发现有什么问题,也欢迎大家在留言榜中提出。

推荐阅读更多精彩内容