bind函数_前端面试题——自己实现bind

前端面试,总会被问到这个问题:

  • 你知道bind怎样使用么?

你心中窃喜,嘿嘿嘿,我看过MDN上的定义,不假思索地答道:“bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。”

你正在等着面试官满意点头的时候,面试官微微一笑,问到:“那你能给我实现一个bind么?”

f87a9823546959393b3ac7451c2ae17b.png

了解bind的实现方法,不仅会帮助我们面试,也是对我们技术的一次提升。废话不说,来不及了,我们一起看看如何自己实现bind。

思路整理

我们先看一个bind的例子:

var 

在这个例子当中,以及对MDN对bind的描述中,bind一共做了四件事情:

  1. bind改变了greeting当中this的指向,使得this指向了obj,因此,this.name的内容是Smiley。最后,bind会返回一个函数。
  2. 我们可以在调用bind的时候,就可以开始给greeting传递参数。上面的例子当中,我们把’the world’作为greeting的第一个参数。
  3. 我们在调用objGreeting这个函数的时候,再传入剩余的参数。上面的例子中,第二个参数是'JS'作为第二个参数lang。
  4. 这个是MDN中提到的:“bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数也可以使用new运算符构造,提供的this值会被忽略,但前置参数仍会提供给模拟函数”。有点晦涩,这也是bind实现中最复杂的一步,使用new关键字这种情况我们一会儿再分析。

思路理顺清楚了,我们可以开始具体分析。

第一步

Function

首先,在bind的函数当中,this的指向是greeting(隐式调用,如果不明白,请看我的文章《前端面试题——十分钟搞懂this》)。我们把this保存在thatFunc,否则如果在返回的函数中直接使用this的话,this的指向会随着调用场景的不同发生改变,而不是一直指向greeting。

这里的arguments中第一个元素就是我们调用bind时候传入的第一个参数obj,使用apply这个函数,调用了greeting,并把greeting中的this指向了obj(对于call和apply不熟悉的小伙伴,可以翻看我的文章《前端面试题——自己实现call和apply》)。另外greeting可能有返回值,因此我们需要return thatFunc.apply(thatArg)。

由于bind函数并不是立即执行,而是要返回一个函数,所以需要把return thatFunc.apply(thatArg)包装在一个函数当中进行返回。

这样,我们就完成了this指向的改变,并返回一个函数。另外,我们还需要判断一下,thatFunc是不是一个function,如果不是的话,就报错,目前代码如下所示:

Function

第二步

我们需要把调用bind时传入的函数参数,继续传入到返回函数中:

Function

由于arguments是类数组,并没有slice这个方法,因此,我们通过使用call这个函数,让arguments也可以使用slice这个方法,从而通过slice(1)获取第二个参数以及以后的参数。(如果Array.prototype.slice.call(arguments, 1)理解起来比较吃力的话,你可以把它当做arguments.slice(1)。当然,这只是帮助理解,如果你直接使用的话,肯定是会报错的)

之后,把得到的args传入apply函数,作为第二个参数。

如果这时,我们跑一下我们最开始的例子,会得到’ Welcome Smiley to bind in undefined’,这是正确的,因为我们lang这个参数还没有传入。

第三步

我们需要把调用objGreeting时候的参数,传入apply的第二个参数当中,补全参数列表。

实际上,我们调用objGreeting的时候,执行的是这个函数:

function

因此,我们只需要把这个函数接受到的arguments,和之前args拼在一起成为一个数组就可以了,如下所示:

var 

很简单吧,我们代码现在如下:

Function

第四步

“bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数也可以使用new运算符构造,提供的this值会被忽略,但前置参数仍会提供给模拟函数”。这是什么意思呢?

我们把最开始使用bind的例子改一点,使用new来调用objGreeting:

var 

我们运行一下,打印出来的结果是:

Welcome Smiley to the world in JS
Undefined

而如果我们把greeting.myBind改为greeting.bind,即,使用我们要模拟的原生bind方法,打印出来的却是:

Welcome undefined to the world in JS
greetingValue

我们的结果和真正bind出来的结果区别在哪里呢?主要有两点:

  1. 调用greeting的时候,this应该指向newObj,这就是因为MDN提到的“绑定函数也可以使用new运算符构造,提供的this值会被忽略”。新的this指向就应该是new运算符构造出来的this指向,即newObj(对于new不熟悉的小伙伴,可以翻看我的文章《前端面试题——自己实现new》)。而在我们的myBind中,返回的匿名函数中,this却还是指向obj。
  2. newObj.value应该打印出greeting的value属性,因为newObj应该“继承”自greeting。而我们的myBind,因为内部没有对prototype进行任何的更新,那么newObj默认继承自myBind返回的匿名函数的原型对象,即Object,Object上没有value,当然打印出来的就是undefined。(关于原型和原型链,我有空会再发文解释)

怎么办呢?我们先把代码放上,然后一点一点分析:

Function

this instanceof fBound这句话中的this,如果是在new关键字调用情况下,会指向newObj,而newObj就是fBound的实例,this instanceof fBound就是true,我们不再使用thatArg作为greeting的this,而是直接使用newObj作为greeting的this 。而当做普通函数调用的时候,this instanceof fBound就是false,greeting中的this依然指向 thatArg。

我们已经满足的区别的第一点,区别的第二点就是通过fBound.prototype = thatFunc.prototype;来实现。如果没有这句话,在new关键字调用下,newObj“继承”自Object;加上这句话之后,我们把fBound的prototype修改为绑定函数的prototype,这样newObj就可以“继承”自greeting了。

不过,上述代码还有一个问题,如果我们修改了fBound的prototype,greeting的prototype也会被修改。因此,我们需要一个中间变量fNOP,让它等于一个空函数,通过fNOP来维护原型关系,并让fBound.prototype与thatFunc.prototype不再指向同一个原型函数:

Function

fNOP和greeting使用同一个prototype,而fBound.prototype实际上是fNOP的一个实例,而这个实例的__proto__才指向的是greeting.prototype。因此,直接修改fBound.prototype并不会修改greeting的prototype。

好啦,希望看这完篇文章,你不仅知道怎样使用bind,也可以写出自己的bind,顺顺利利通过面试。祝大家前端学习一切顺利!

关注我的公众号:前端三剑客。分享前端面试与算法面试的心得与总结!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值