一般情况下,函数中的this指向被调用的对象,如果函数被直接调用,则指向window。
但是有些方法是可以改变函数this指向的,例如call(),apply(),bind()
他们的相同点和不同点分别是:
call和apply在改变this指向时会执行函数,bind不会,它会返回一个新的函数,新的函数中的this指向会被改变
call和bind在传参上类似,都是将参数单独传递,而apply是将参数作为一个数组进行传递,但是bind能同时在外部和内部传参(与它返回的是一个新函数有关,外部传参就相当于在执行新函数时传参)
接下来就是我对这三种方法的源码的实现
call()
定义在Function的原型上就是为了函数调用的
target为传递的第一个参数,即函数的this的指向的对象,如果不传则为window。
arg为传递的参数除去第一个(即除去target后的所有参数)
...arg中的...为ES6的展开运算符
在target.fn=this中,这个this的指向在开头有讲过,this指向调用这个函数的对象,即调用call的函数(简称fun),但是如果直接执行fun,那么fun的this指向window,显然是不行的,所以我们在target上添加一个属性为fn,fn为fun,然后再用执行target.fn间接执行fun,此时因为是被target打点调用,所以this指向target。
最后一步为删除target上的fn属性,不然target会多出一个属性,这显然是不太合适的,所以直接调用delete方法删除这个属性
想要理解这里就只需要谨记一点,this指向被调用的对象,如果函数被直接调用,那么就指向window(可以理解为直接调用就是被window调用)
Function.prototype.myCall = function () {
var target = arguments[0] || window;
var arg = Array.from(arguments).slice(1);
target.fn = this;
target.fn(...arg);
delete target.fn
}
如果理解了call(),那么apply就好理解了,因为它与call()的区别就在于传参的形式不同
Function.prototype.myApply = function () {
var target = arguments[0] || window;
var arg = arguments[1];
target.fn = this;
target.fn(...arg);
delete target.fn
}
bind因为返回值不同,所以它是最为难理解的一个
这个new.target为检测函数或构造方法是否是通过new运算符被调用的,此处也是为了迎合bind被调用的不同场景下的不同返回值,刚开始看着可能会比较不明白,只需要多尝试,去掉判断条件将bind与myBind 在被new时进行对比就能知道含义了
Function.prototype.myBind = function () {
var target = arguments[0] || window;
var arg = Array.from(arguments).slice(1);
var that = this;
return function () {
if (!new.target) {
var args = arg.concat(Array.from(arguments));
target.fn = that;
target.fn(...args);
delete target.fn;
} else {
return new that();
}
}
}