原生JS实现bind()函数
一、bind()函数的两个特性:
1、bind和curring,函数科里化
function add(a, b, c) {
var i = a+b+c;
console.log(i);
return i;
}
var func = add.bind(undefined, 100);//给add()传了第一个参数a
func(1, 2);//103,继续传入b和c
var func2 = func.bind(undefined, 200);//给func2传入第一个参数,也就是b,此前func已有参数a=100
func2(10);//310,继续传入c,100+200+10
可以利用此种特性方便代码重用,如下,可以不同的页面中只需要配置某几项,前面几项固定的配置可以选择用bind函数先绑定好,讲一个复杂的函数拆分成简单的子函数。
2、bind和new
function foo() {
this.b = 100;
console.log(this.a);
return this.a;
}
var func = foo.bind({a:1});
func();//1
new func();//undefined {b:100},可以看到此时上面的bind并不起作用
函数中的return除非返回的是个对象,否则通过new返回的是个this,指向一个空对象,空对象原型指向foo.prototype,空对象的b属性是100。也就是说通过new的方式创建一个对象,bind()函数在this层面上并不起作用,但是需要注意在参数层面上仍起作用,如下:
function foo(c) {
this.b = 100;
console.log(this.a);
console.log(c);
return this.a;
}
var func = foo.bind({a:1},20);
new func();//undefined 20,通过new创建对象func,bind绑定的c依旧起作用
二、bind实现
了解完以上两个特性,再来看看bind()的实现:
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis || window,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
第3行:传入oThis就是foo.bind({a:1})中传入的对象{a:1};
第4行:判断调用此bind方法的对象是不是一个函数function,若不是则报错;
第10行:
(1)因为函数自带的arguments属性并不是一个数组,只是一个类数组,不具有slice这些方法,所以用call方法给slice()指定this为arguments,让arguments也可以实现slice()方法。
(2)后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。 这里是将bind函数的参数数组取出来,第一个参数不要(就是不要oThis)也就是要被绑定方法的那个对象
(3)arguments参数只有在函数调用执行的时候才存在,也就是当var func = foo.bind({a:1});的时候,调用了bind,此时aArgs是一个空数组。如果是var func = foo.bind({a:1}, 2),那么aArgs = [2];
第12行,13行,20行,21行:创建了一个空对象FNOP,并将这个空对象的原型指向foo的原型;
然后又将func/fBound的原型指向一个新的FNOP实例,这个步骤完成了给func/fBound拷贝一个FNOP的prototype即this/foo的prototype。
其实这几句就相当于fBound.prototype = Object.create(this.prototype);
第14行:
(1)给 fBound/func return一个fToBind/foo对象;
(2)这里的this指的是调用func()时的执行环境;直接调用func()的时候,this指向的是全局对象,那么结果是oThis/{a:1},这样就可以让这个fToBind的this指向这个传进来的对象oThis;
(3)bind()同时也会传参数:aArgs.concat(Array.prototype.slice.call(arguments))
,
【注意】这里的arguments是调用此函数时的arguments,也就是func()的执行环境,和上面的arguments(bind的执行环境)不一样,在此例中,此时的arguments是空数组,因为并没有给func()传参数。这段contact的意思就是把bind()中传的参数和func()中传的参数连起来,来实现上面提到的bind的科里性。
(4)如果通过new func()来调用,this会指向一个空对象,这个空对象的原型会指向构造器的prototype的属性,也就是func/fBound的prototype属性。此时this instanceof fNOP
为true,那么返回的是this就是当前正常的this;相当于忽略掉bind的this的影响,实现了上述的bind特性二:bind和new。
那么想想为什么要给func/fBound拷贝一个FNOP的prototype即this/foo的prototype?没有实现这个会怎样?
我们知道bind()函数其实是实现了this的指定和参数的传递; 实际中的new func()其实相当于创建了func()一个新实例,使用的构造函数是func,它只为新对象定义了【默认的】属性和方法。也就是Object.create(this.prototype)的作用,如果不把foo的prototype拷贝个func,那么这里的new func()就没法得到foo默认的属性。 如图我把那两行注释掉后,za没办法获取到this.b的值