本质
call、aplly、bind 本质都是改变 this 的指向,不同点 call、aplly 是直接调用函数,bind 是返回一个新的函数。call 跟 aplly 就只有参数上不同。
手写实现
bind 实现
- 箭头函数的 this 永远指向它所在的作用域
- 在方法调用模式下,this 总是指向调用它所在方法的对象;this 的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数特殊)
- 函数作为构造函数用 new 关键字调用时,不应该改变其 this 指向,因为 new绑定 的优先级高于 显示绑定 和 硬绑定
// bind 实现
Function.prototype.djBind = function (thisArg) {
if (typeof this !== "function") {
throw TypeError("Bind must be called on function");
}
// 拿到参数args,为了传给调用者
const args = Array.prototype.slice.call(arguments, 1),
self = this,
nop = function () {},
bound = function () {
// this instanceof nop, 判断是否使用 new 来调用 bound
// 如果是 new 来调用的话,this的指向就是其实例,
// 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
return self.apply(
this instanceof nop ? this : thisArg,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
if (this.prototype) {
nop.prototype = this.prototype;
}
// 修改绑定函数的原型指向
bound.prototype = new nop();
return bound;
};
Test bind
const bar = function() {
console.log(this.name, arguments);
};
bar.prototype.name = 'bar';
const foo = {
name: 'foo'
};
const bound = bar.djBind(foo, 22, 33, 44);
new bound(); // bar, [22, 33, 44] this指向bar
bound(); // foo, [22, 33, 44
call 实现
Function.prototype.djCall = function(thisArg) {
if(typeof this !== 'function') {
throw TypeError("call must be called on function");
}
const args = Array.prototype.slice.call(arguments, 1);
thisArg = thisArg || window;
// 将调用call函数的对象添加到thisArg的属性中
thisArg.fn = this;
// 执行该属性, 在thisArg上调用this方法;
const result = thisArg.fn(...args);
delete thisArg.fn;
return result;
}
问题
- fn 同名覆盖问题
可以使用Symbol,解决同名冲突问题; - ...展开操作符问题
apply 实现
Function.prototype.djApply = function(thisArg) {
if(typeof this !== 'function') {
throw TypeError("call must be called on function");
}
const args = arguments[1];
thisArg = thisArg || window;
// 将调用call函数的对象添加到thisArg的属性中
thisArg.fn = this;
// 执行该属性
const result = thisArg.fn(...args);
delete thisArg.fn;
return result;
}
Test call && apply
const bar = function() {
console.log(this.name, arguments);
};
bar.prototype.name = 'bar';
const foo = {
name: 'foo'
};
bar.mycall(foo, 1, 2, 3); // foo [1, 2, 3]
bar.myaplly(foo, [1, 2, 3]); // foo [1, 2, 3]
this
JS中的this是一个非常简单的东西,只需要理解它的执行规则;
call/apply/bind可以显式绑定, 这里就不说了;如上所述;
主要这些场隐式绑定的场景讨论:
- 全局上下文
- 直接调用函数
- 对象.方法的形式调用
- DOM事件绑定(特殊)
- new构造函数绑定
- 箭头函数
- 全局上下文
全局上下文默认this指向window, 严格模式下指向undefined。 - 直接调用函数
比如:
let obj = {
a: function() {
console.log(this);
}
}
let func = obj.a;
func();
这种情况是直接调用。this相当于全局上下文的情况。
- 对象.方法的形式调用
还是刚刚的例子,我如果这样写:
obj.a();
这就是对象.方法的情况,this指向这个对象
- DOM事件绑定
onclick和addEventerListener中 this 默认指向绑定事件的元素。
IE比较奇异,使用attachEvent,里面的this默认指向window。 - new+构造函数
此时构造函数中的this指向实例对象。 - 箭头函数?
箭头函数没有this, 因此也不能绑定。里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined)。比如:
let obj = {
a: function() {
let do = () => {
console.log(this);
}
do();
}
}
obj.a(); // 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj
优先级: new > call、apply、bind > 对象.方法 > 直接调用。