前言
在了解怎么实现bind之前我们先来了解一下bind的功能,正所谓知己知彼百战不殆。
原生bind的功能
bind的功能主要用来强制绑定函数的this,然后返回一个新的函数
function show (...args) {
console.log(this, ...args)
}
var obj = {
x: 20
}
var newShow0 = show.bind()
var newShow1 = show.bind(obj)
var newShow2 = show.bind(obj, 1, 2, 3)
var newShow3 = show.bind(obj, 1, 2, 3)
var newShow4 = show.bind(obj, 1, 2, 3)
newShow1()
newShow1.isShow = true
console.log(newShow1.isTrue, show.isTrue)
newShow2()
newShow3(4)
new newShow4()
new newShow4(4)
实现效果如下:
总结bind的功能
以上例子我们可以看出bind的功能主要有一下几点:
1. 函数show调用bind方法,需要传递参数target, ...args
2. 调用bind方法后的函数返回一个新的函数,向新的函数对象添加属性不会修改原本函数。
3. 函数newShow在执行的时候,具体的功能还是用的show的功能,只不过show
的this
改为了target
。如果不任何参数,那么show
的this
指向window
4. 函数newShow在执行的时候可以传递参数,并且传递的参数会拼接到bind的时候传递的参数后面
5. 当以new
的构造函数形式执行newShow时,show
的this
依旧是show
而不是bind时传入的obj
实现方法
总结了bind的以上功能之后,我们可以依据功能书写源码。
实现绑定this,传递参数后返回一个新函数
Function.prototype.bind = function (target) {
// target是bind绑定this的对象
var self = this; // 保存当前函数的this
// 从第二个参数开始截取实参列表并转换为数组
var args = Array.prototype.slice.call(arguments, 1);
var f = function () {
// 将执行返回的新方法时传递的参数转换成数组
var _args = Array.prototype.slice.call(arguments)
// 根据target的有无确定this,然后将bind的参数和执行时的参数拼接
// 并返回一个Function函数执行之后的结果
return self.apply(target || window, args.concat(_args))
}
return f
}
根据以上代码我们就完成了上述功能的1、3、4功能,还剩下两个功能。
用圣杯模式实现新函数和老函数原型的解绑
Function.prototype.bind = function (target) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var f = function () {
var _args = Array.prototype.slice.call(arguments)
return self.apply(target || window, args.concat(_args))
}
// 圣杯模式:通过new 创建的中间构造函数temp,实现f和temp的继承
var temp = function () {};
temp.prototype = self.prototype;
f.prototype = new temp()
return f
}
通过圣杯模式我们可以实现新函数和老函数的关系解绑,当我们给新函数增加属性的时候就不会影响老函数。
接下来我们就只需要实现最后一个功能:当以new
的构造函数形式执行newShow时,show
的this
依旧是show
而不是bind时传入的obj
实现new的形式构造函数
注意我们此时并没有改变f
的this
指向,我们只是修改了执行f
时show
的this
在我们没有使用自己的bind方法时,在此之前我们想一个问题:当我们通过new的形式时,我们的this是指向原函数show的,那么中间就必然存在一个判断问题,判断什么呢?判断当前的f函数的this是否存在于temp的原型链上
。为什么这么说呢?因为当我们使用bind的时候,在没有new的情况下f
的this
是指向window,而在使用new
时,f
的this
指向show。
由圣杯模式的继承关系我们可以得出一个结论:f
的原型指向的是temp
。所以我们只需判断当前新函数f
的在没在temp
的原型链上来确定apply函数的第一个参数。
Function.prototype.bind = function (target) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var f = function () {
var _args = Array.prototype.slice.call(arguments)
// 由于f的祖先是self的原型,所以这里写self也可以
return self.apply(this instanceof temp ? this : (target || window), args.concat(_args))
}
var temp = function () {};
temp.prototype = self.prototype;
f.prototype = new temp()
return f
}
通过以上例子我们就实现了bind源码内部解构,小伙伴们可以自己去试试