【Javascript】如何使用 this 关键字,call()、bind()、apply() 方法
学习 call(), apply(), 和 bind() 方法是很重要的,因为它们让你能够控制 JavaScript 中 this 的上下文。在某些情况下,默认的 this 行为可能不符合预期,比如当你从一个对象借用方法到另一个对象,或者是在回调函数中保持正确的上下文时,这些方法提供了灵活性和控制力。通过掌握它们,你可以编写出更加高效、可复用并且上下文感知的函数,这在复杂的应用中尤其有用。
在我们深入了解 call(), apply(), 和 bind() 方法之前,先来了解一下 'this' 关键字及其工作机制。
'this' 关键字
让我们通过下面的要点来理解什么时候以及 this 关键字指的是什么:
在一个对象的方法中, this 指向该对象。在对象内部定义的方法中,this 将指向拥有该方法的对象。
在一个普通函数中, this 指向全局对象。在非严格模式下,如果函数是在全局上下文中调用(而不是作为对象的方法),this 指向的是全局对象(在浏览器中是 window)。
在严格模式下的函数中, this 是 undefined。如果函数不是某个对象的方法,并且没有绑定到特定上下文(通过 call, apply, 或 bind),那么在严格模式下 this 将是 undefined。
在事件处理程序中, this 指向接收事件的元素。当触发事件时,this 指向的是触发事件的 HTML 元素。
点击移除我!
在这里,this 指向的是接收到 onclick 事件的按钮元素本身。
在箭头函数中, this 的行为有所不同。箭头函数没有自己的 this 上下文。相反,this 以词法形式继承自创建箭头函数时周围的上下文。这意味着箭头函数内部的 this 指向的是其外部函数或上下文的 this 值。
const person = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(`嗨,我是 ${this.name}`);
}, 1000);
}
};
person.greet(); // 输出: 嗨,我是 Alice
在这个例子中,setTimeout 内部的箭头函数继承了 greet 方法的 this,后者指向 person 对象。
call() 方法
call() 方法允许你“借用”一个对象上的函数或方法,并用另一个对象来使用它,只需把另一个对象作为第一个参数传递即可。第一个参数成为了函数内部的 this 值,而其他参数紧随其后。
call() 方法并不会创建新的函数;它只是用提供的上下文和参数运行现有的函数。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
person.fullName.call(person1, "Oslo", "Norway");
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,call() 被用来执行 person 对象中的 fullName 方法,但是使用了 person1 的数据(firstName 和 lastName),附加的参数则是 “Oslo” 和 “Norway”。
apply() 方法
apply() 方法和 call() 方法非常相似。主要区别在于参数是如何传递给函数的。使用 apply() 时,你可以将参数作为一个数组(或类数组对象)传递,而不是单独传递。
像 call() 一样,apply() 方法也不会创建新的函数。它立即执行函数,使用提供的上下文 (this 值) 和参数。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,apply() 被用来调用 person 对象中的 fullName 方法,但是上下文 (this) 设置为了 person1。参数 “Oslo” 和 “Norway” 作为一个数组传递。
bind() 方法
JavaScript 中的 bind() 方法让你能够设置一个函数或方法的上下文(this 值),就像 call() 和 apply() 一样。然而,不同于 call() 和 apply(),bind() 方法不会立即调用函数。相反,它返回一个新的函数,其中 this 值被设置为你指定的对象。
const person = {
fullName: function(city, country) {
console.log(this.firstName + " " + this.lastName + " 正准备去 " + city + ", " + country + ".");
}
}
const person1 = {
firstName: "John",
lastName: "Doe"
}
const func = person.fullName.bind(person1);
func("Oslo", "Norway");
// 输出: John Doe 正准备去 Oslo, Norway.
在这个例子中,bind() 创建了一个新的函数 func,其中 this 值被设置为 person1。这个函数不会立即被调用,但你可以稍后传入参数 “Oslo” 和 “Norway” 来调用它。
示例:带有多上下文的集中式记录器
这里有一个小型但复杂的应用示例,在这个例子中使用 call(), apply(), 或 bind() 可以提高效率——尤其是在处理用于记录目的的函数的部分应用时:
假设你有一个集中式的记录函数,用于记录不同用户执行的动作。使用 bind() 可以有效地将 this 上下文设置为不同的用户,避免了重复代码。
const logger = {
logAction: function(action) {
console.log(`${this.name} (ID: ${this.id}) 执行了: ${action}`);
}
};
const user1 = { name: "Alice", id: 101 };
const user2 = { name: "Bob", id: 202 };
// 为不同的用户创建新的记录函数
const logForUser1 = logger.logAction.bind(user1);
const logForUser2 = logger.logAction.bind(user2);
// 执行动作时无需手动传递用户上下文
logForUser1("login");
// 输出: Alice (ID: 101) 执行了: login
logForUser2("purchase");
// 输出: Bob (ID: 202) 执行了: purchase
为什么这样更高效?
上下文重用: 每次记录动作时,你都不需要手动传递用户上下文。上下文 (this) 只绑定一次,使记录变得可重用且干净。
模块化: 如果你需要添加更多用户或动作,你可以快速地将它们绑定到 logger 上,而不必改变函数本身,使你的代码保持 DRY(不要做重复的工作)。