文章目录
首先,我们都知道
JavaScript
中的this
指向分为软绑定及硬绑定。并且this
的指向与执行上下文有关系。
在全局执行上下文中,this
指向 全局对象(window/global
),在对象调用其内部方法时,this
指向调用的地方,这就称为软绑定。JavaScript中this指向详解
那么如何使用硬绑定呢?这就需要使用JavaScript
中内置的call/aplly/bind
方法来实现了。
1.call
1.1.基本用法
Mdn
给出的介绍如下。翻译过来就是 :call
方法会将this
指向传入的 第一个参数,call
方法可以接收多个参数- 调用函数执行
1.2.call使用的例子
软绑定的时候我们是这么使用的:
console.log(this); // this -> window
let obj = {
name: "刘德华",
say: function () {
console.log("say");
console.log(this); // this -> obj
},
};
obj.say();
输出结果如下:
那么现在我们有一个新的对象,如何将 this
挂载到新对象上呢?
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args); // 此处通过解构赋值拿到所有的参数
console.log(this); // this -> o
},
};
// 此处调用 call,第一个参数就是要绑定的 this,后面就是要传递的参数
obj.say.call(o, o.sing, 1, 2, 3);
可以看到此时打印的 this
已经不是 obj 了,而是新定义的 o:
1.3.自己实现一个 call 方法
思路:
- 在
Function.prototype
上定义一个myCall
方法,并且obj.say
调用自定义的myCall
方法 - 接收第一个参数,就是传递进来要硬绑定的新对象
- 在新对象内部创建一个
fn
函数,将 当前this
赋值给fn
函数(当前this
是obj.say
) - 调用新对象中的
fn
函数执行(即调用obj.say
执行) - 完成后将新对象中的
fn
函数删除(避免占用内存)
Function.prototype.myCall = function (context, ...args) {
console.log(this); // this -> obj.say
console.log(args); // 拿到参数做些什么
context.fn = this;
context.fn();
delete context.fn;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function () {
console.log(this); // this -> o
},
};
obj.say.myCall(o, o.sing, 1, 2, 3);
这样实现有一个问题,当我们第一个参数传递的是 null
的时候是会报错的:
原生的 call
方法是不会报这个错误的,因此需要对参数进行兜底处理:
当然了,还应该将 接收的多个参数,传递给 obj.say
方法使用:
Function.prototype.myCall = function (context, ...args) {
console.log(this); // this -> obj.say
context = context ? context : window; // 参数兜底处理
context.fn = this;
context.fn(...args); // 将拿到的参数,传递给 obj.say 使用
delete context.fn; // 完成后就删除,避免浪费内存
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args); // 拿到参数做些什么
console.log(this); // this -> o
},
};
obj.say.myCall(null, o.sing, 1, 2, 3);
2.apply
2.1.基本用法
- 改变
this
指向 - 传递一个参数数组
- 调用函数执行
2.2. apply使用的例子
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args);
console.log(this); // this -> o
},
};
obj.say.apply(o, [o.sing, 1, 2, 3]);
除了入参不同,其他和 call
没什么区别:
2.3. 自己实现一个apply方法
Function.prototype.myApply = function (context, args) {
context = context ? context : window;
context.fn = this;
if (!args) {
context.fn();
} else if (Array.isArray(args)) {
context.fn(...args);
} else {
return TypeError("args is not a Array");
}
delete context.fn;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args);
console.log(this); // this -> o
},
};
obj.say.myApply(o, [o.sing, 1, 2, 3]);
3.bind
3.1.基本使用
- 改变
this
指向 - 接收多个参数
- 返回一个新的函数
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args);
console.log(this); // this -> o
},
};
let res = obj.say.bind(o, [o.sing, 1, 2, 3]);
res();
参数可以是任意形式,参数列表/数组都可以:
3.2.手动实现一个bind
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(bindArgs)
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
name: "刘德华",
say: function (...args) {
console.log(args);
console.log(this); // this -> o
},
};
let res = obj.say.myBind(o, [o.sing, 1, 2, 3]);
res();
4.call/apply/bind的异同
- 相同点
- 改变
this
指向
- 改变
- 不同点:
call
:接受的是参数列表apply
:接收的是一个参数数组bind
:返回一个新的函数,需要再次调用才执行