JS语言理解13 bind函数的实现
面试经常遇到的问题,以前的实现考虑还是不太全面,总结了一下。
定义
bind
的作用和call
与apply
类似,区别在于使用上。bind
的执行的结果返回的是绑定了一个对象的新函数
先看一个使用例子:
1 | var obj = { name: 'Jay' }; |
MDN上的定义:
bind()
方法创建了一个新的函数,在bind()
被调用时,这个新函数的this
会被设定为bind
方法传入的第一个参数,其余的参数将作为新函数的参数供调用时使用。
从上面的定义来看,bind
函数的功能包括:
- 改变原函数的
this
指向,绑定this
- 返回原函数的拷贝,并预传参数
要注意的,当使用new
调用bind
返回的参数时,bind
绑定this
失效。这是由于上面提到过的this
绑定的优先级,new
的优先级高于bind
简易版
在面试过程中,无数次遇到过这个问题,如何自己实现一个bind
函数,一般情况下,都知道了用apply
/call
来实现
1 | Function.prototype.myBind = function (thisArg) { |
貌似没有问题了,实现了bind
上面提到的绑定this
和复制函数的功能,还附带实现了传参的实现。
但是有一点,对于绑定优先级的处理还有一些问题,this
有4种绑定规则:
- 默认绑定
- 隐式绑定
- 显示绑定
new
绑定
四种绑定规则的优先级从上到下依次递增,默认绑定的优先级最低,new
绑定的优先级最高,而bind
方法就是显示绑定的一种,优先级应该低于new
绑定
先复习一下new
调用构造函数时的过程:
- 创建一个全新的对象
- 这个对象被执行
[[Prototype]]
连接 - 将这个对象绑定到构造函数中的
this
- 如果函数没有返回其他对象,则
new
操作符调用的函数则会返回这个对象
先看一下正常的bind
后的函数遇到new
操作符,表示是如何的:
1 | const obj = {}; |
首先直接调用bind
后的参数,这时候this
指向obj
,所以obj.name
变成了jay
,然后通过new
操作符调用Person
,根据上面的优先级,new
的优先级更高,所以this
会绑定为创建的新对象p2
,所以对name
的更改是对p2
的name
的更改,obj
的name
保持不变
再来看看我们的myBind
方法遇到this
后的表现:
1 | const obj = {}; |
而我们的简易版bind
方法,一旦绑定了this
后,当遇到new
操作符后也不会更改,而是固定在obj
上,所以在new
的过程中绑定的this
仍然是我们myBind
绑定的对象obj
,所以p2.name
所以对简易版的bind
还需要优化
优化版
想要解决上面的问题,关键点就是在于处理new
的第三步,我们的myBind
函数识别是new
调用,那么就不再将this
绑定到我们传入的thisArg
对象,而是绑定为函数本身调用的this
那么问题就转换为了,如何判断一个函数是普通调用,还是通过new
操作符调用。在new
的过程中,会首先建立新建对象的原型继承,然后绑定新建对象到构造函数的this
,也就是下面的过程
1 | let p1 = new Person(); |
通过第二步和第三步,this
就成为了Person
的一个实例,可以通过this insetanceof Person
来判断,所以可以对上面的myBind
进行优化:
1 | Function.prototype.myBind = function (thisArg) { |
再来验证一下效果:
1 | const obj = {}; |
myBind
的效果与原生bind
的效果一致,new
操作符改变了bind
的this
指向。
MDN上的bind
实现,增加了维护原型关系的步骤,但是我并不是太理解这样做的必要性,我理解new
的实现就直接将this
与fBound
的原型链进行了关联,可以通过instanceof
判断
1 | if (!Function.prototype.bind) { |
这个疑问先放在这里,看以后有没有机会弄明白,或者哪位大神来指点吧。(2019-09-23)